Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67d838433d | |||
| e3aeff002c | |||
| 09ccd4339c | |||
| 03aa38f1b4 | |||
| 9389a883f1 | |||
| 31fa26f03a | |||
| ce96d278cd | |||
| 7166b76bad | |||
| 540db0e210 | |||
| 59b75476ca | |||
| 0029b34698 | |||
| 8e03e89764 | |||
| 3b1850af9d | |||
| 223029785c | |||
| ce8be45ea5 | |||
| e6d8c6dbfe | |||
| 74ec53040c | |||
| e251a85d7a | |||
| a16533f11b | |||
| b979f7ae7d | |||
| b1e9878849 | |||
| b47fa91d6c | |||
| e9e4a0ca82 | |||
| f5e2ce5ca9 | |||
| cdbbbb5316 | |||
| c5967e9995 | |||
| 5f61ed8fcb | |||
| c3e4a456d8 | |||
| 080942b49d | |||
| 1220168faf | |||
| 4b8d5ab8b1 | |||
| 9d80ddb6ac | |||
| 150828b806 | |||
| 47e358dc18 | |||
| 47c9293849 | |||
| 0f5582f100 | |||
| 3a78a6baf9 | |||
| 0f2cf711db | |||
| 55c202516d | |||
| 4e1a0576e1 | |||
| 6d70b41844 | |||
| 4fa30abb4b | |||
| 68a86c38b5 | |||
| 3099371864 | |||
| 42653434e0 | |||
| 25475d1057 | |||
| ce27e8e5cf | |||
| b17a004b83 | |||
| ccfada5ca9 | |||
| 59ee532bf6 | |||
| 03073bd597 | |||
| 674bdc7192 | |||
| 76579e3059 | |||
| 426e23d8be | |||
| 269498453e | |||
| 32330b420e | |||
| 2ba0ecf428 | |||
| 2b5471283e | |||
| dbde798221 | |||
| 68f5515588 | |||
| 891ad5ad25 | |||
| c02994c836 | |||
| 569ca99e66 | |||
| f13e512712 | |||
| b28525057a | |||
| b9d631e968 | |||
| d5473d7cae | |||
| cd51ba41d8 | |||
| aef95bdf78 | |||
| f633b9e330 |
@@ -0,0 +1,49 @@
|
||||
# Triangles code style.
|
||||
# Conservative: do not reflow long lines, do not reorganize includes.
|
||||
# This config is enforced *only on changed lines* via `git clang-format` in CI,
|
||||
# so it shapes new/edited code without touching legacy files until they're touched.
|
||||
|
||||
BasedOnStyle: LLVM
|
||||
Language: Cpp
|
||||
Standard: c++17
|
||||
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
ContinuationIndentWidth: 4
|
||||
AccessModifierOffset: -4
|
||||
|
||||
ColumnLimit: 0 # Don't reflow long lines — too disruptive for legacy code.
|
||||
ReflowComments: false
|
||||
|
||||
BreakBeforeBraces: Attach
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
|
||||
PointerAlignment: Left
|
||||
DerivePointerAlignment: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
|
||||
NamespaceIndentation: None
|
||||
FixNamespaceComments: true
|
||||
|
||||
# Includes: don't shuffle — header order in this codebase is load-bearing
|
||||
# (e.g. main.cpp's mix of project + system headers carries platform meaning).
|
||||
SortIncludes: false
|
||||
IncludeBlocks: Preserve
|
||||
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MaxEmptyLinesToKeep: 2
|
||||
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignTrailingComments: true
|
||||
|
||||
# Don't auto-add braces to single-statement bodies — too invasive.
|
||||
InsertBraces: false
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# Triangles clang-tidy config.
|
||||
#
|
||||
# Goal: catch real bugs in new/edited code without drowning in noise from
|
||||
# legacy patterns. Enforced *diff-only* in CI (changed lines on PRs).
|
||||
#
|
||||
# Conservative starter set. Graduate checks to WarningsAsErrors only after
|
||||
# the codebase is clean for that check.
|
||||
|
||||
Checks: >
|
||||
-*,
|
||||
bugprone-*,
|
||||
performance-*,
|
||||
readability-misleading-indentation,
|
||||
readability-redundant-control-flow,
|
||||
readability-redundant-smartptr-get,
|
||||
readability-redundant-string-cstr,
|
||||
readability-redundant-string-init,
|
||||
readability-string-compare,
|
||||
modernize-use-nullptr,
|
||||
modernize-use-override,
|
||||
modernize-deprecated-headers,
|
||||
cppcoreguidelines-init-variables,
|
||||
cppcoreguidelines-pro-type-member-init,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-implicit-widening-of-multiplication-result,
|
||||
-bugprone-narrowing-conversions,
|
||||
-bugprone-branch-clone,
|
||||
-bugprone-signed-char-misuse,
|
||||
-bugprone-reserved-identifier,
|
||||
-bugprone-unchecked-optional-access,
|
||||
-performance-no-int-to-ptr,
|
||||
-performance-avoid-endl
|
||||
|
||||
# Warn-only initially. Once a check is clean repo-wide we can promote it here.
|
||||
WarningsAsErrors: ''
|
||||
|
||||
# Run on project sources; skip vendored/generated code.
|
||||
HeaderFilterRegex: '^.*src/(?!json/nlohmann_json|leveldb|lz4|tor/tor-src).*\.h$'
|
||||
|
||||
FormatStyle: file
|
||||
|
||||
CheckOptions:
|
||||
- key: readability-identifier-naming.IgnoreMainLikeFunctions
|
||||
value: '1'
|
||||
- key: cppcoreguidelines-init-variables.IncludeStyle
|
||||
value: 'google'
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Revisions listed here are skipped by `git blame` when --ignore-revs-file
|
||||
# is configured. GitHub honors this file automatically.
|
||||
#
|
||||
# Add the SHA of any large mechanical reformat / rename / mass-style commit
|
||||
# below, with a one-line comment.
|
||||
#
|
||||
# Example:
|
||||
# abc1234567890abcdef # repo-wide clang-format (no behavior change)
|
||||
#
|
||||
# To enable locally:
|
||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
@@ -2,7 +2,7 @@ name: Build All Platforms
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [master, cpp20-modernization]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
branches: [master]
|
||||
@@ -14,13 +14,15 @@ jobs:
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake ninja-build \
|
||||
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
|
||||
libevent-dev libminiupnpc-dev zlib1g-dev
|
||||
librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
@@ -37,6 +39,52 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: cd build && ctest --output-on-failure || true
|
||||
|
||||
test-linux-sanitizers:
|
||||
# ASan + UBSan build of the daemon + unit tests. Allowed to fail until
|
||||
# findings are triaged — see .github/workflows/lint.yml comment block.
|
||||
# Once the test suite is clean under sanitizers, drop continue-on-error.
|
||||
runs-on: ubuntu-22.04
|
||||
continue-on-error: true
|
||||
env:
|
||||
# ASan: leak detection off by default (BDB and OpenSSL produce noise on shutdown).
|
||||
# Re-enable once we've quieted the legitimate suspects.
|
||||
ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1:abort_on_error=1:print_stacktrace=1:strict_string_checks=1:detect_stack_use_after_return=1"
|
||||
# UBSan: print full stack traces on first error and exit non-zero.
|
||||
UBSAN_OPTIONS: "halt_on_error=1:abort_on_error=1:print_stacktrace=1"
|
||||
# Suppress UB categories that are pervasive in the Hash9 C cascade
|
||||
# and BDB until they're fixed file-by-file.
|
||||
SAN_FLAGS: "-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=undefined -fno-sanitize=alignment,signed-integer-overflow,vptr"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake ninja-build \
|
||||
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
|
||||
librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev
|
||||
|
||||
- name: Configure with sanitizers
|
||||
run: |
|
||||
cmake -B build-san -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_C_FLAGS="$SAN_FLAGS" \
|
||||
-DCMAKE_CXX_FLAGS="$SAN_FLAGS" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \
|
||||
-DCMAKE_SHARED_LINKER_FLAGS="$SAN_FLAGS" \
|
||||
-DBUILD_QT=OFF \
|
||||
-DBUILD_DAEMON=ON \
|
||||
-DBUILD_TESTS=ON \
|
||||
-DUSE_UPNP=OFF
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build-san -j$(nproc)
|
||||
|
||||
- name: Run unit tests under sanitizers
|
||||
run: cd build-san && ctest --output-on-failure
|
||||
|
||||
build-windows-qt:
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
@@ -44,6 +92,8 @@ jobs:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
@@ -61,6 +111,7 @@ jobs:
|
||||
mingw-w64-x86_64-libevent
|
||||
mingw-w64-x86_64-miniupnpc
|
||||
mingw-w64-x86_64-zlib
|
||||
mingw-w64-x86_64-rocksdb
|
||||
|
||||
- name: Set VERSION
|
||||
run: |
|
||||
@@ -194,6 +245,8 @@ jobs:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
@@ -209,6 +262,7 @@ jobs:
|
||||
mingw-w64-x86_64-libevent
|
||||
mingw-w64-x86_64-miniupnpc
|
||||
mingw-w64-x86_64-zlib
|
||||
mingw-w64-x86_64-rocksdb
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
@@ -256,6 +310,8 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set VERSION
|
||||
run: |
|
||||
@@ -274,7 +330,7 @@ jobs:
|
||||
sudo apt-get install -y build-essential cmake ninja-build \
|
||||
qtbase5-dev qttools5-dev-tools \
|
||||
libboost-all-dev libssl-dev libdb++-dev \
|
||||
libleveldb-dev libevent-dev libminiupnpc-dev zlib1g-dev
|
||||
libleveldb-dev librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
@@ -373,6 +429,8 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set VERSION
|
||||
run: |
|
||||
@@ -390,7 +448,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake ninja-build \
|
||||
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
|
||||
libevent-dev libminiupnpc-dev zlib1g-dev
|
||||
librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
@@ -503,6 +561,8 @@ jobs:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set VERSION
|
||||
run: |
|
||||
@@ -517,7 +577,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew install cmake ninja qt@5 openssl@3 boost berkeley-db@5 leveldb libevent miniupnpc
|
||||
brew install cmake ninja qt@5 openssl@3 boost berkeley-db@5 leveldb rocksdb libevent miniupnpc
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
# Diff-only enforcement: clang-format and clang-tidy run only on lines changed
|
||||
# in the PR. Existing files keep their current style until they're edited.
|
||||
# See .clang-format and .clang-tidy for the rule sets.
|
||||
|
||||
jobs:
|
||||
clang-format-diff:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Need merge-base with target branch to compute the diff.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-format-15
|
||||
sudo ln -sf /usr/bin/clang-format-15 /usr/local/bin/clang-format
|
||||
|
||||
- name: Check format on changed lines
|
||||
run: |
|
||||
BASE_SHA=$(git merge-base origin/${{ github.base_ref }} HEAD)
|
||||
echo "Comparing against merge-base: $BASE_SHA"
|
||||
|
||||
# git-clang-format prints a diff if any changed line violates style.
|
||||
# --diff exits non-zero when reformatting would change something.
|
||||
OUTPUT=$(git clang-format --diff "$BASE_SHA" -- '*.cpp' '*.h' '*.hpp' '*.cc' || true)
|
||||
|
||||
if [ -z "$OUTPUT" ] || [ "$OUTPUT" = "no modified files to format" ] || [ "$OUTPUT" = "clang-format did not modify any files" ]; then
|
||||
echo "clang-format: clean"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::error::clang-format wants to change the following on lines you touched."
|
||||
echo "Run \`git clang-format $BASE_SHA\` locally and commit the result."
|
||||
echo "$OUTPUT"
|
||||
exit 1
|
||||
|
||||
clang-tidy-diff:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dependencies + clang-tidy
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake ninja-build clang-tidy-15 \
|
||||
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
|
||||
librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev
|
||||
sudo ln -sf /usr/bin/clang-tidy-15 /usr/local/bin/clang-tidy
|
||||
|
||||
- name: Configure (export compile_commands.json)
|
||||
run: |
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBUILD_QT=OFF \
|
||||
-DBUILD_DAEMON=ON \
|
||||
-DBUILD_TESTS=ON \
|
||||
-DUSE_UPNP=OFF
|
||||
|
||||
- name: Generate build artifacts that headers depend on
|
||||
# build.h, qt UI headers, etc. — clang-tidy needs them to parse sources.
|
||||
run: cmake --build build --target generate_build_info
|
||||
|
||||
- name: Run clang-tidy on changed lines
|
||||
run: |
|
||||
BASE_SHA=$(git merge-base origin/${{ github.base_ref }} HEAD)
|
||||
echo "Comparing against merge-base: $BASE_SHA"
|
||||
|
||||
# clang-tidy-diff.py ships with clang-tidy; runs tidy only on changed lines.
|
||||
DIFF_SCRIPT=$(dpkg -L clang-tidy-15 | grep clang-tidy-diff.py | head -1)
|
||||
if [ -z "$DIFF_SCRIPT" ]; then
|
||||
DIFF_SCRIPT=/usr/share/clang/clang-tidy-diff.py
|
||||
fi
|
||||
echo "Using: $DIFF_SCRIPT"
|
||||
|
||||
# -p1 strips the leading "a/"/"b/" from git diff paths.
|
||||
# -path=build points clang-tidy at compile_commands.json.
|
||||
# -iregex restricts to project sources (not vendored).
|
||||
git diff -U0 "$BASE_SHA" -- 'src/*.cpp' 'src/*.h' \
|
||||
':(exclude)src/json/nlohmann_json.hpp' \
|
||||
':(exclude)src/leveldb/*' \
|
||||
':(exclude)src/lz4/*' \
|
||||
':(exclude)src/tor/tor-src/*' \
|
||||
| python3 "$DIFF_SCRIPT" -p1 -path build \
|
||||
-iregex '.*\.(cpp|cc|h|hpp)$' \
|
||||
-j$(nproc) || EXIT=$?
|
||||
|
||||
# Warn-only initially. Flip this to `exit ${EXIT:-0}` once we're clean.
|
||||
exit 0
|
||||
@@ -1,3 +1,6 @@
|
||||
# Per-user Claude Code settings (machine-specific paths/permissions)
|
||||
.claude/
|
||||
|
||||
# Build artifacts
|
||||
*.o
|
||||
*.exe
|
||||
@@ -7,8 +10,12 @@
|
||||
*.a
|
||||
/dist/
|
||||
build/
|
||||
build2/
|
||||
build_*/
|
||||
release/
|
||||
debug/
|
||||
build_err*.txt
|
||||
*build_err.txt
|
||||
/Makefile
|
||||
Makefile.Debug
|
||||
Makefile.Release
|
||||
@@ -24,6 +31,7 @@ ui_*.h
|
||||
qrc_*.cpp
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
*.qm
|
||||
|
||||
# Blockchain data
|
||||
*.dat
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
[submodule "src/tor/tor-src"]
|
||||
path = src/tor/tor-src
|
||||
url = https://gitlab.torproject.org/tpo/core/tor.git
|
||||
[submodule "src/secp256k1"]
|
||||
path = src/secp256k1
|
||||
url = https://github.com/bitcoin-core/secp256k1
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
# IBD Stall Bug Analysis — `triangles_v5`
|
||||
|
||||
**Date:** 2026-04-24
|
||||
**Symptom:** Node syncs from genesis, accepts blocks normally up to a point (observed: ~6284), then permanently stalls. `askfor_queue=0`, `orphans=0`, no new blocks ever arrive.
|
||||
|
||||
---
|
||||
|
||||
## Root Cause: Header Sync Cache Exhaustion Without Refill
|
||||
|
||||
The bug is a **broken feedback loop** between the header planner and block downloader. The node drains its header cache faster than it refills it, and once the cache is empty, the pipeline freezes with no recovery path.
|
||||
|
||||
### The Pipeline (how it should work)
|
||||
|
||||
```
|
||||
getheaders → 2000 headers → mapHeaderSync → AskFor(MSG_BLOCK) → mapAskFor → getdata → block received → ProcessBlock → QueueHeaderSyncBlocksParallel (refill)
|
||||
↑
|
||||
└── every 500 blocks: getheaders+getblocks to ALL peers
|
||||
```
|
||||
|
||||
### The Bug Path (how it actually dies)
|
||||
|
||||
**Step 1: Initial header fetch**
|
||||
- `version` handler sends `getblocks` + `getheaders` to peer
|
||||
- Peer responds with up to 2000 headers → stored in `mapHeaderSync`
|
||||
- `QueueHeaderSyncBlocksParallel(512)` queues up to 512 blocks via `AskFor()`
|
||||
|
||||
**Step 2: Blocks download and consume headers**
|
||||
- Blocks arrive, `ProcessBlock()` accepts them
|
||||
- Each accepted block calls `MarkHeaderSyncBlockAccepted()` which **erases** it from `mapHeaderSync`
|
||||
- After accepting, `QueueHeaderSyncBlocksParallel(512)` tries to queue more from remaining `mapHeaderSync` entries
|
||||
|
||||
**Step 3: The critical gap — header cache runs dry**
|
||||
- The header cache holds at most `MAX_HEADER_SYNC_CACHE = 15000` entries
|
||||
- But `getheaders` only returns **2000 headers per batch**
|
||||
- The `HEADER_DOWNLOAD_WINDOW = 512` means only 512 blocks are in-flight at once
|
||||
- So blocks are consumed from the cache faster than headers are fetched
|
||||
- Each accepted block erases its entry; if all 2000 headers are downloaded before the next `getheaders` fires...
|
||||
|
||||
**Step 4: Cache empties → pipeline dies**
|
||||
- `mapHeaderSync` becomes empty
|
||||
- `hashBestHeaderSync` is recomputed to `0` (by `RecomputeBestHeaderSync()`)
|
||||
- The refill condition `if (hashBestHeaderSync != 0)` at line ~3599 evaluates **false**
|
||||
- No more blocks are queued, ever
|
||||
|
||||
**Step 5: No recovery mechanism kicks in**
|
||||
- The `ContinueHeaderSync()` call only fires when `vHeaders.size() >= 2000` (full batch)
|
||||
- If the last batch was smaller (partial response, or exactly 2000 consumed), **no new `getheaders` is sent**
|
||||
- The pipeline refill every 500 blocks only fires **when a block is received** — but no blocks are coming
|
||||
- The stall detection sends `getblocks` (not `getheaders`), which produces `inv` messages → `AskFor()` for individual blocks
|
||||
- But `getblocks` uses `CBlockLocator` with exponential spacing, which maps to an old block → peer sends `inv` for blocks we already have → walk-forward logic tries to progress but may loop or stall
|
||||
|
||||
### Why It's Worse on Fast Connections / Sync-from-Zero
|
||||
|
||||
- Blocks download fast (PoS blocks are tiny)
|
||||
- All 2000 headers are consumed quickly
|
||||
- The window between "all headers consumed" and "need more headers" is tiny
|
||||
- On slow Tor connections, the 15-minute TTL eviction (`PruneHeaderSync`) adds a second failure mode: headers that took too long to download get evicted, creating gaps in `GetHeaderSyncDownloadPath()`
|
||||
|
||||
---
|
||||
|
||||
## Affected Code Locations
|
||||
|
||||
| File | Line(s) | Issue |
|
||||
|------|---------|-------|
|
||||
| `main.cpp` | 137 | `MAX_HEADER_SYNC_CACHE = 15000` — cache is large but `getheaders` only returns 2000 |
|
||||
| `main.cpp` | 138 | `HEADER_DOWNLOAD_WINDOW = 512` — window is smaller than header batch |
|
||||
| `main.cpp` | 298-345 | `AddHeaderSyncNode()` / `PruneHeaderSync()` — TTL eviction can create gaps in the download path |
|
||||
| `main.cpp` | 380-396 | `GetHeaderSyncDownloadPath()` — walks back from tip; **breaks on first gap** in `mapHeaderSync` chain |
|
||||
| `main.cpp` | 455-461 | `MarkHeaderSyncBlockAccepted()` — erases from `mapHeaderSync`, may set `hashBestHeaderSync = 0` |
|
||||
| `main.cpp` | 3599-3606 | Block-accepted refill — **guarded by `hashBestHeaderSync != 0`**, skips when cache is empty |
|
||||
| `main.cpp` | 4972-4976 | `getheaders` continuation — **only fires on full batch** (`vHeaders.size() >= 2000`) |
|
||||
| `main.cpp` | 5110-5121 | Pipeline refill every 500 blocks — **only fires when blocks arrive**, useless during stall |
|
||||
| `main.cpp` | 5952-5980 | Stall detection — sends `getblocks` (not `getheaders`), can't restart header planner |
|
||||
|
||||
---
|
||||
|
||||
## Fix Options
|
||||
|
||||
### Fix A: Refill headers when cache runs dry (minimal, targeted)
|
||||
|
||||
In the block-accepted handler, when `hashBestHeaderSync == 0`, send `getheaders` to all peers:
|
||||
|
||||
```cpp
|
||||
// After the existing refill (line ~3599)
|
||||
if (hashBestHeaderSync == 0)
|
||||
{
|
||||
// Header cache exhausted — request more headers from all peers
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
if (!pnode->fClient && pnode->nVersion != 0)
|
||||
{
|
||||
pnode->pindexLastGetHeadersBegin = NULL;
|
||||
pnode->PushGetHeaders(pindexBest, uint256(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fix B: Also send getheaders in stall detection (defense in depth)
|
||||
|
||||
In the stall handler (line ~5968), alongside the `getblocks`, also send `getheaders`:
|
||||
|
||||
```cpp
|
||||
pto->PushGetBlocks(...); // existing
|
||||
pto->pindexLastGetHeadersBegin = NULL;
|
||||
pto->PushGetHeaders(pindexBest, uint256(0)); // ADD THIS
|
||||
```
|
||||
|
||||
### Fix C: Don't let the header cache fully drain (robustness)
|
||||
|
||||
In `QueueHeaderSyncBlocksParallel()`, stop consuming the last N entries from the cache to keep the chain intact. When only `HEADER_DOWNLOAD_WINDOW` entries remain, trigger a `getheaders` continuation before consuming more.
|
||||
|
||||
### Fix D: Periodic getheaders in SendMessages loop (most robust)
|
||||
|
||||
Add a periodic `getheaders` request in the `SendMessages` loop, similar to how stall detection already sends periodic `getblocks`. This ensures headers are always being fetched regardless of block progress:
|
||||
|
||||
```cpp
|
||||
// In SendMessages, alongside stall detection:
|
||||
if (!pto->fClient && IsInitialBlockDownload() && hashBestHeaderSync == 0)
|
||||
{
|
||||
static int64_t nLastHeaderRequest = 0;
|
||||
if (GetTime() - nLastHeaderRequest >= 30)
|
||||
{
|
||||
pto->pindexLastGetHeadersBegin = NULL;
|
||||
pto->PushGetHeaders(pindexBest, uint256(0));
|
||||
nLastHeaderRequest = GetTime();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommended Fix
|
||||
|
||||
**Fix A + Fix B together** — minimal code change, covers both the block-accepted path and the stall recovery path. Fix D adds belt-and-suspenders protection in the main loop.
|
||||
|
||||
---
|
||||
|
||||
## Secondary Issue: TTL Eviction Creating Path Gaps
|
||||
|
||||
`PruneHeaderSync()` evicts entries older than 15 minutes. If block download is slow (Tor, slow peers), entries at the beginning of the download path can be evicted while entries at the end still exist. `GetHeaderSyncDownloadPath()` walks back from the tip and **breaks on the first missing entry**, making the entire tail of the cache unreachable.
|
||||
|
||||
**Fix:** In `GetHeaderSyncDownloadPath()`, skip gaps instead of breaking:
|
||||
|
||||
```cpp
|
||||
while (hashTip != 0 && !mapBlockIndex.count(hashTip))
|
||||
{
|
||||
auto mi = mapHeaderSync.find(hashTip);
|
||||
if (mi == mapHeaderSync.end())
|
||||
break; // Currently breaks — could skip to next known ancestor instead
|
||||
vPath.push_back(hashTip);
|
||||
hashTip = mi->second.header.hashPrevBlock;
|
||||
}
|
||||
```
|
||||
|
||||
This is a secondary concern but contributes to cache exhaustion on high-latency connections.
|
||||
@@ -1,94 +0,0 @@
|
||||
# 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
|
||||
@@ -1,76 +0,0 @@
|
||||
# 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.
|
||||
+65
-3
@@ -6,13 +6,15 @@ if(POLICY CMP0167)
|
||||
endif()
|
||||
|
||||
project(Triangles
|
||||
VERSION 5.9.2
|
||||
VERSION 6.0.0
|
||||
DESCRIPTION "Cryptographic Triangles Wallet"
|
||||
LANGUAGES C CXX
|
||||
)
|
||||
|
||||
# ── C++ Standard ──
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
# C++20 required: RocksDB headers in MSYS2/Homebrew (8.x+) use `using enum`
|
||||
# and defaulted operator== on user-defined types, both C++20-only.
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
@@ -71,7 +73,7 @@ include(AddCompilerFlags)
|
||||
# ── Find required dependencies ──
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(Boost 1.71 REQUIRED COMPONENTS
|
||||
filesystem program_options thread chrono
|
||||
program_options thread chrono
|
||||
)
|
||||
if(BUILD_TESTS)
|
||||
find_package(Boost REQUIRED COMPONENTS unit_test_framework)
|
||||
@@ -95,6 +97,66 @@ if(USE_ZMQ)
|
||||
pkg_check_modules(ZMQ REQUIRED IMPORTED_TARGET libzmq)
|
||||
endif()
|
||||
|
||||
# RocksDB is now a hard dependency: backs both the chain database and the
|
||||
# secure-messaging store (smessage). Probe in order:
|
||||
# 1. CMake config package (MSYS2, Homebrew, vcpkg, recent Linux)
|
||||
# 2. pkg-config (some Linux distros, no .cmake files)
|
||||
# 3. Manual find_path/find_library (Ubuntu 22.04's librocksdb-dev ships
|
||||
# neither a CMake config nor a .pc file)
|
||||
# In all paths, a target named RocksDB::rocksdb is exposed for consumers.
|
||||
find_package(RocksDB CONFIG QUIET)
|
||||
if(NOT RocksDB_FOUND)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(RocksDB IMPORTED_TARGET QUIET rocksdb)
|
||||
endif()
|
||||
endif()
|
||||
if(NOT TARGET RocksDB::rocksdb AND NOT TARGET PkgConfig::RocksDB)
|
||||
find_path(ROCKSDB_INCLUDE_DIR
|
||||
NAMES rocksdb/db.h
|
||||
PATHS /usr/include /usr/local/include
|
||||
)
|
||||
find_library(ROCKSDB_LIBRARY
|
||||
NAMES rocksdb
|
||||
PATHS /usr/lib /usr/lib/x86_64-linux-gnu /usr/local/lib
|
||||
)
|
||||
if(NOT ROCKSDB_INCLUDE_DIR OR NOT ROCKSDB_LIBRARY)
|
||||
message(FATAL_ERROR
|
||||
"RocksDB not found. Install librocksdb-dev (Ubuntu/Debian), "
|
||||
"rocksdb (Homebrew), or mingw-w64-x86_64-rocksdb (MSYS2).")
|
||||
endif()
|
||||
add_library(RocksDB::rocksdb UNKNOWN IMPORTED)
|
||||
set_target_properties(RocksDB::rocksdb PROPERTIES
|
||||
IMPORTED_LOCATION "${ROCKSDB_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${ROCKSDB_INCLUDE_DIR}"
|
||||
)
|
||||
message(STATUS "Found RocksDB (manual probe): ${ROCKSDB_LIBRARY}")
|
||||
endif()
|
||||
|
||||
# libsecp256k1 — vendored as a git submodule under src/secp256k1. Provides
|
||||
# ECDSA signing/verification, pubkey recovery (via the recovery module), and
|
||||
# ECDH for secure messaging. Configure the submodule's build for our needs:
|
||||
# only ECDH + recovery, none of the test/benchmark/extra-module bloat, and
|
||||
# don't install (we link statically against the in-tree target).
|
||||
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/src/secp256k1/CMakeLists.txt")
|
||||
message(FATAL_ERROR
|
||||
"src/secp256k1 is empty. Run: git submodule update --init --recursive")
|
||||
endif()
|
||||
set(SECP256K1_DISABLE_SHARED ON CACHE INTERNAL "")
|
||||
set(SECP256K1_INSTALL OFF CACHE INTERNAL "")
|
||||
set(SECP256K1_BUILD_BENCHMARK OFF CACHE INTERNAL "")
|
||||
set(SECP256K1_BUILD_TESTS OFF CACHE INTERNAL "")
|
||||
set(SECP256K1_BUILD_EXHAUSTIVE_TESTS OFF CACHE INTERNAL "")
|
||||
set(SECP256K1_BUILD_CTIME_TESTS OFF CACHE INTERNAL "")
|
||||
set(SECP256K1_BUILD_EXAMPLES OFF CACHE INTERNAL "")
|
||||
set(SECP256K1_ENABLE_MODULE_ECDH ON CACHE INTERNAL "")
|
||||
set(SECP256K1_ENABLE_MODULE_RECOVERY ON CACHE INTERNAL "")
|
||||
set(SECP256K1_ENABLE_MODULE_EXTRAKEYS OFF CACHE INTERNAL "")
|
||||
set(SECP256K1_ENABLE_MODULE_SCHNORRSIG OFF CACHE INTERNAL "")
|
||||
set(SECP256K1_ENABLE_MODULE_MUSIG OFF CACHE INTERNAL "")
|
||||
set(SECP256K1_ENABLE_MODULE_ELLSWIFT OFF CACHE INTERNAL "")
|
||||
add_subdirectory(src/secp256k1 EXCLUDE_FROM_ALL)
|
||||
|
||||
if(BUILD_QT)
|
||||
find_package(Qt5 5.9 REQUIRED COMPONENTS Core Gui Widgets)
|
||||
find_package(Qt5 COMPONENTS LinguistTools QUIET)
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
# Embedded Tor Integration Guide for Triangles
|
||||
|
||||
This guide explains how to compile Tor as a static library (`libtor.a`) and link
|
||||
it directly into the Triangles wallet binary so that every node automatically
|
||||
runs a Tor hidden service without needing an external Tor installation.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
trianglesd / triangles-qt
|
||||
├── tor_embedded.cpp ← calls tor_run_main() in a background thread
|
||||
├── tor_process.cpp ← fallback: launches external tor binary (already works)
|
||||
├── onion_v3.cpp ← V3 onion address generation / SOCKS5 proxy logic
|
||||
└── libtor.a ← aggregate static Tor library (built from official source)
|
||||
```
|
||||
|
||||
When compiled with `ENABLE_TOR_EMBEDDED`, the wallet calls `tor_run_main()` from
|
||||
`tor_api.h` on a dedicated thread. This gives the wallet a SOCKS5 proxy on
|
||||
`127.0.0.1:19099` and a V3 hidden service on port 24112 (the P2P port).
|
||||
|
||||
When compiled **without** the flag, `tor_embedded.cpp` falls back to the external
|
||||
`tor_process.cpp` which searches for and launches a system `tor` binary.
|
||||
|
||||
## Step 1: Add Tor as a Git Submodule
|
||||
|
||||
```bash
|
||||
cd /path/to/triangles
|
||||
git submodule add https://gitlab.torproject.org/tpo/core/tor.git src/tor/tor-src
|
||||
cd src/tor/tor-src
|
||||
git checkout release-0.4.9 # latest stable branch as of 2026
|
||||
```
|
||||
|
||||
This puts the full Tor source at `src/tor/tor-src/`.
|
||||
Current imported checkout in this repo: `release-0.4.9` at commit `1442ca4`.
|
||||
There is also a helper build script at `src/tor/build-libtor.sh`.
|
||||
|
||||
## Step 2: Build libtor.a
|
||||
|
||||
Tor uses autotools. Build it as a static library:
|
||||
|
||||
```bash
|
||||
cd src/tor/tor-src
|
||||
|
||||
# Install Tor build dependencies
|
||||
sudo apt install autoconf automake libtool pkg-config \
|
||||
libssl-dev libevent-dev zlib1g-dev
|
||||
|
||||
# Generate configure script
|
||||
./autogen.sh
|
||||
|
||||
# Configure for static library build (disable unneeded modules)
|
||||
./configure \
|
||||
--enable-static-tor \
|
||||
--disable-module-relay \
|
||||
--disable-module-dirauth \
|
||||
--disable-asciidoc \
|
||||
--disable-manpage \
|
||||
--disable-html-manual \
|
||||
--disable-unittests \
|
||||
--disable-tool-name-check \
|
||||
--with-openssl-dir=/usr \
|
||||
--with-libevent-dir=/usr \
|
||||
--with-zlib-dir=/usr \
|
||||
--prefix=/usr/local
|
||||
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
Or from the repo root:
|
||||
```bash
|
||||
./src/tor/build-libtor.sh
|
||||
```
|
||||
|
||||
After building, the static libraries are in `src/tor/tor-src/`:
|
||||
- `libtor.a`
|
||||
- `src/lib/libtor-*.a` (multiple component libs)
|
||||
|
||||
The header `src/feature/api/tor_api.h` provides the public C API:
|
||||
```c
|
||||
tor_main_configuration_t *tor_main_configuration_new(void);
|
||||
int tor_main_configuration_set_command_line(tor_main_configuration_t *cfg,
|
||||
int argc, char *argv[]);
|
||||
int tor_run_main(const tor_main_configuration_t *);
|
||||
void tor_main_configuration_free(tor_main_configuration_t *);
|
||||
```
|
||||
|
||||
## Step 3: Build Triangles with Embedded Tor
|
||||
|
||||
### Linux (makefile.unix)
|
||||
|
||||
```bash
|
||||
cd src
|
||||
|
||||
# Point to Tor's built libraries and headers
|
||||
make -f makefile.unix \
|
||||
USE_TOR_EMBEDDED=1
|
||||
```
|
||||
|
||||
You may need to adjust the `-l` flags in the makefile depending on the exact
|
||||
library names Tor produces. Check `src/tor/tor-src/` after building:
|
||||
|
||||
```bash
|
||||
find tor/tor-src -name '*.a' | sort
|
||||
```
|
||||
|
||||
On the imported `release-0.4.9` checkout in this repo, the simplest working
|
||||
link path is the aggregate `libtor.a` plus the normal dependency libraries.
|
||||
|
||||
### Windows (triangles-qt.pro)
|
||||
|
||||
Add to `triangles-qt.pro`:
|
||||
```qmake
|
||||
qmake "USE_TOR_EMBEDDED=1" \
|
||||
"TOR_SOURCE_ROOT=src/tor/tor-src"
|
||||
```
|
||||
|
||||
Both build systems now default to:
|
||||
- source root: `src/tor/tor-src`
|
||||
- include path: `src/tor/tor-src/src/feature/api`
|
||||
- library path: `src/tor/tor-src`
|
||||
- embedded Tor library: `-ltor`
|
||||
|
||||
On Windows, the imported Tor `0.4.9.5` build also needed:
|
||||
- `-llzma`
|
||||
- `-lzstd`
|
||||
- `-liphlpapi`
|
||||
- `-lshlwapi` (already linked by Triangles)
|
||||
|
||||
## Step 4: Wire into init.cpp
|
||||
|
||||
The global hooks `StartEmbeddedTor()` and `StopEmbeddedTor()` need to be called
|
||||
from `init.cpp`. Add these calls:
|
||||
|
||||
### In AppInit2() (after network init, before starting node):
|
||||
```cpp
|
||||
#include "tor/tor_embedded.h"
|
||||
|
||||
// Near the end of AppInit2, after network initialization:
|
||||
if (!StartEmbeddedTor()) {
|
||||
printf("WARNING: Embedded Tor failed to start. .onion connectivity unavailable.\n");
|
||||
// Non-fatal: wallet works without Tor, just no .onion
|
||||
}
|
||||
```
|
||||
|
||||
### In Shutdown():
|
||||
```cpp
|
||||
StopEmbeddedTor();
|
||||
```
|
||||
|
||||
## Step 5: Configure SOCKS Proxy for Outbound Connections
|
||||
|
||||
After Tor starts, the wallet needs to route `.onion` connections through the
|
||||
SOCKS5 proxy. In `net.cpp`, after Tor is initialized:
|
||||
|
||||
```cpp
|
||||
// If embedded Tor is running, use its SOCKS proxy for .onion addresses
|
||||
CTorEmbedded* tor = CTorEmbedded::GetInstance();
|
||||
if (tor->IsRunning()) {
|
||||
// Set proxy for .onion connections
|
||||
proxyType addrProxy(CService("127.0.0.1", tor->GetSocksPort()), 5);
|
||||
SetNameProxy(addrProxy);
|
||||
}
|
||||
```
|
||||
|
||||
## Runtime Flags
|
||||
|
||||
The embedded Tor respects these command-line flags:
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `-notor` | false | Disable Tor entirely |
|
||||
| `-torsocks=PORT` | 19099 | SOCKS5 proxy port |
|
||||
| `-torhsport=PORT` | 24112 | Hidden service virtual port |
|
||||
|
||||
## File Layout After Integration
|
||||
|
||||
```
|
||||
src/tor/
|
||||
├── tor-src/ ← git submodule (official Tor repo)
|
||||
│ └── src/
|
||||
│ ├── lib/libtor-*.a
|
||||
│ └── feature/api/tor_api.h
|
||||
│ └── libtor.a
|
||||
├── tor_embedded.h ← CTorEmbedded class header
|
||||
├── tor_embedded.cpp ← implementation (calls tor_run_main)
|
||||
├── tor_process.h ← external Tor process manager (fallback)
|
||||
├── tor_process.cpp
|
||||
├── onion_v3.h ← V3 onion address utilities
|
||||
├── onion_v3.cpp
|
||||
├── anonymize.h ← data dir helpers
|
||||
├── anonymize.cpp
|
||||
└── LICENSE
|
||||
```
|
||||
|
||||
## Reference: How VERGE (XVG) Does It
|
||||
|
||||
VERGE uses the same pattern. Their implementation is at:
|
||||
- `src/torcontroller.cpp` (~100 lines)
|
||||
- They use `tor_main()` (older API, pre-0.4.5)
|
||||
- Git submodule at `src/tor/` pointing to `release-0.4.8` branch
|
||||
- Build Tor as part of their `depends/` system
|
||||
|
||||
Key difference: modern Tor (0.4.5+) uses `tor_run_main()` with a configuration
|
||||
object instead of raw `tor_main(int argc, char** argv)`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Tor fails to bootstrap**: Check firewall rules. Tor needs outbound TCP to the
|
||||
Tor network (ports 80, 443, 9001, 9030).
|
||||
|
||||
**Link errors with libtor**: Prefer the aggregate `libtor.a` from the top level
|
||||
of the Tor build tree. On the imported Windows/MSYS2 build in this repo, the
|
||||
minimal verified link set was:
|
||||
```
|
||||
-ltor -levent -lssl -lcrypto -lz -llzma -lzstd -lws2_32 -liphlpapi -lshlwapi
|
||||
```
|
||||
|
||||
**OpenSSL version mismatch**: Both Tor and Triangles must link against the same
|
||||
OpenSSL version (3.x). If Tor was built against a different OpenSSL, rebuild it
|
||||
with the same `--with-openssl-dir`.
|
||||
@@ -1,210 +0,0 @@
|
||||
# Triangles Modernization Roadmap
|
||||
|
||||
**Goal:** Make TRI faster to sync, safer for wallets, and more useful as a currency — without breaking consensus.
|
||||
|
||||
**Invariant:** Any change that modifies block validation, stake modifier computation, transaction format, or signature verification MUST preserve exact consensus with existing v5.x nodes. When in doubt, test against a synced v5.8.1 node.
|
||||
|
||||
---
|
||||
|
||||
## Priority 1: Faster Syncing (High Impact, Low Risk)
|
||||
|
||||
### 1.1 Update Checkpoints (Easy, Immediate)
|
||||
**Problem:** Last hardcoded checkpoint is at block 2,186,940. `IsInitialBlockDownload()` returns false past this point, causing orphan limit to drop from 4000 to 750 — exactly what caused the fork deadlock.
|
||||
|
||||
**Fix:** Add checkpoints every ~50,000 blocks up to current height (~2,207,000+).
|
||||
```cpp
|
||||
// src/checkpoints.cpp - add recent checkpoints
|
||||
{2190000, uint256("...")},
|
||||
{2195000, uint256("...")},
|
||||
{2200000, uint256("...")},
|
||||
{2205000, uint256("...")},
|
||||
{2210000, uint256("...")},
|
||||
```
|
||||
**Risk:** None — checkpoints are only used for IBD detection and quick rejection of clearly wrong chains.
|
||||
|
||||
### 1.2 Increase Post-Checkpoint Orphan Limit (Easy)
|
||||
**Problem:** 750 orphans after IBD is too low for a low-peer network. During the fork incident, 750 orphans filled up and the node deadlocked.
|
||||
|
||||
**Fix:**
|
||||
```cpp
|
||||
// src/main.h
|
||||
static const unsigned int MAX_ORPHAN_BLOCKS = 2000; // was 750
|
||||
```
|
||||
**Risk:** Slightly more memory usage during forks. Worth it for resilience.
|
||||
|
||||
### 1.3 Parallel Block Download (Medium Effort)
|
||||
**Problem:** Current implementation downloads blocks sequentially from one peer at a time during IBD.
|
||||
|
||||
**Fix:** Increase batch sizes and allow concurrent block downloads from multiple peers:
|
||||
```cpp
|
||||
// src/main.cpp
|
||||
// During IBD, request blocks from multiple peers simultaneously
|
||||
unsigned int nGetDataBatchSize = IsInitialBlockDownload() ? 8000 : 1000; // was 4000
|
||||
```
|
||||
**Risk:** Low — larger batch sizes are already proven in Bitcoin forks.
|
||||
|
||||
### 1.4 Header-First Sync (Medium Effort)
|
||||
**Problem:** Node downloads full blocks before validating headers. A bad peer can waste bandwidth.
|
||||
|
||||
**Fix:** Download and validate all headers first (compact ~80 bytes each), then download full blocks only for the best chain.
|
||||
- Separate `getheaders`/`headers` message handling
|
||||
- Download blocks only for the best header chain
|
||||
- Reduces wasted bandwidth during forks by 95%+
|
||||
|
||||
### 1.5 Bootstrap Over HTTPS with Resume (Easy)
|
||||
**Problem:** Built-in bootstrap (`-bootstrap`) uses raw TCP and can't resume interrupted downloads.
|
||||
|
||||
**Fix:** The existing `bootstrap.cpp` already supports downloading. Add:
|
||||
- Resume support (Range headers)
|
||||
- SHA256 verification of downloaded archive
|
||||
- Better progress reporting
|
||||
- Fallback mirrors
|
||||
|
||||
---
|
||||
|
||||
## Priority 2: Wallet Safety (Critical)
|
||||
|
||||
### 2.1 Automatic Wallet Backup Before Dangerous Operations (Easy)
|
||||
**Problem:** Corrupt wallet = lost funds. No automatic backup before risky operations.
|
||||
|
||||
**Fix:** In `walletdb.cpp`, before any rewrite:
|
||||
```cpp
|
||||
// Before wallet.dat rewrite, copy to wallet.dat.bak
|
||||
if (boost::filesystem::exists(pathWallet)) {
|
||||
boost::filesystem::copy_file(pathWallet, pathWallet + ".bak",
|
||||
boost::filesystem::copy_option::overwrite_if_exists);
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Detect and Report BDB Corruption (Easy)
|
||||
**Problem:** BDB corruption silently corrupts wallet. User doesn't know until it's too late.
|
||||
|
||||
**Fix:** Add wallet integrity check on load:
|
||||
```cpp
|
||||
// In CWallet::LoadWallet()
|
||||
// After opening, verify BDB environment is healthy
|
||||
// If DB_RUNRECOVERY, auto-salvage and warn user
|
||||
```
|
||||
|
||||
### 2.3 Wallet.dat Versioning (Medium Effort)
|
||||
**Problem:** Single wallet.dat file. If it corrupts during write, funds are lost.
|
||||
|
||||
**Fix:** Implement copy-on-write wallet saves:
|
||||
- Write new wallet data to `wallet.dat.new`
|
||||
- Atomically rename `wallet.dat` → `wallet.dat.old`, `wallet.dat.new` → `wallet.dat`
|
||||
- Keep last 3 wallet revisions
|
||||
- On load, try wallet.dat first, fall back to wallet.dat.old if corrupt
|
||||
|
||||
### 2.4 Seed Phrase / HD Wallet (High Effort, High Impact)
|
||||
**Problem:** Losing wallet.dat = losing everything. No recovery mechanism.
|
||||
|
||||
**Fix:** Implement BIP39/BIP44 HD wallet as optional upgrade:
|
||||
- Generate 12/24-word seed phrase on new wallet creation
|
||||
- Derive all keys from seed deterministically
|
||||
- Import seed on any device to recover wallet
|
||||
- Keep backward compatibility with existing non-HD wallets
|
||||
|
||||
---
|
||||
|
||||
## Priority 3: Network Resilience (Medium Impact)
|
||||
|
||||
### 3.1 Better Peer Management (Medium Effort)
|
||||
**Problem:** Low peer counts (2-6) lead to fork divergence. No prioritization of reliable peers.
|
||||
|
||||
**Fix:**
|
||||
- Peer reliability scoring (track which peers provide valid blocks)
|
||||
- Prefer peers that are ahead and on the same chain
|
||||
- Automatic disconnection of stale/forked peers
|
||||
- Increase default `maxconnections` from 64 to 128
|
||||
|
||||
### 3.2 Compact Block Relay (High Effort)
|
||||
**Problem:** Full blocks are sent even when the receiver likely already has most transactions.
|
||||
|
||||
**Fix:** Implement BIP 152 compact blocks:
|
||||
- Send block header + short transaction IDs
|
||||
- Receiver fills in from mempool, only requests missing transactions
|
||||
- Reduces bandwidth by ~90% during normal operation
|
||||
|
||||
### 3.3 DNS Seed Infrastructure (Easy)
|
||||
**Problem:** `dnsseed=0` when Tor-only means no automatic peer discovery.
|
||||
|
||||
**Fix:** Run a DNS seed server that resolves to known reliable onion addresses:
|
||||
```
|
||||
seed.cryptographic-triangles.org → returns onion addresses of healthy nodes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Priority 4: User Experience (Medium Impact)
|
||||
|
||||
### 4.1 Progress Reporting for IBD (Easy)
|
||||
**Problem:** Users see "downloading blocks..." with no useful progress indicator.
|
||||
|
||||
**Fix:**
|
||||
- Report `headers` vs `blocks` progress separately
|
||||
- Show estimated time remaining based on download speed
|
||||
- Log progress every 1000 blocks (currently every 5000)
|
||||
- Qt wallet: update progress bar more frequently
|
||||
|
||||
### 4.2 Staking Dashboard Improvements (Easy)
|
||||
**Problem:** Qt wallet shows staking info but not clearly.
|
||||
|
||||
**Fix:**
|
||||
- Show expected time to stake more prominently
|
||||
- Display staking weight as percentage of network
|
||||
- Notify when stake is found (system notification)
|
||||
- Show "staking" indicator in system tray
|
||||
|
||||
### 4.3 Transaction Fee Estimation (Medium Effort)
|
||||
**Problem:** No fee estimation. Users guess.
|
||||
|
||||
**Fix:** Track recent block inclusion rates by fee level, provide fee recommendations.
|
||||
|
||||
---
|
||||
|
||||
## Priority 5: Code Modernization (Low Urgency, Good Hygiene)
|
||||
|
||||
### 5.1 C++17/20 Features
|
||||
- Replace raw pointers with smart pointers where safe
|
||||
- Use `std::optional`, `std::string_view`, `std::filesystem`
|
||||
- Replace boost::filesystem with std::filesystem (C++17)
|
||||
|
||||
### 5.2 Build System
|
||||
- CMake is already in place (good)
|
||||
- Add sanitizers (ASAN, UBSAN) to CI
|
||||
- Static analysis with clang-tidy
|
||||
|
||||
### 5.3 Testing
|
||||
- Current test coverage is thin
|
||||
- Add unit tests for:
|
||||
- Checkpoint validation
|
||||
- Stake modifier computation
|
||||
- Bootstrap download/resume
|
||||
- Wallet BDB recovery
|
||||
- Orphan block handling
|
||||
|
||||
---
|
||||
|
||||
## What NOT to Change
|
||||
|
||||
These are consensus-critical and must remain identical:
|
||||
- Block validation rules
|
||||
- Stake modifier computation (`ComputeNextStakeModifier`)
|
||||
- Transaction signature verification
|
||||
- Block reward schedule
|
||||
- PoW/PoS target computation
|
||||
- Chain trust / difficulty adjustment
|
||||
- Message serialization format
|
||||
- Protocol version handshaking
|
||||
|
||||
Any change to these requires a coordinated network upgrade (hard fork).
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. **This week:** Update checkpoints (1.1), increase orphan limit (1.2), wallet backup before save (2.1)
|
||||
2. **Next week:** Better progress reporting (4.1), increase batch size (1.3)
|
||||
3. **Month 1:** Wallet versioning (2.3), bootstrap resume (1.5)
|
||||
4. **Month 2:** Header-first sync (1.4), peer reliability (3.1)
|
||||
5. **Month 3+:** HD wallet (2.4), compact blocks (3.2)
|
||||
@@ -1,300 +0,0 @@
|
||||
# OpenClaw Bootstrap Snapshot Guide
|
||||
|
||||
## Purpose
|
||||
|
||||
This document tells OpenClaw exactly how to update the existing Triangles bootstrap server so new wallets download a ready-to-use snapshot instead of downloading `blk0001.dat` and rebuilding the index locally.
|
||||
|
||||
This guide matches the current wallet code in:
|
||||
|
||||
- `src/bootstrap.cpp`
|
||||
- `src/bootstrap.h`
|
||||
- `src/checkpoints.cpp`
|
||||
- `src/version.h`
|
||||
|
||||
## What The Wallet Actually Does
|
||||
|
||||
When a fresh wallet bootstraps, it:
|
||||
|
||||
1. Downloads `http://bootstrap.cryptographic-triangles.org/bootstrap.tar.gz`
|
||||
2. Extracts it into the data directory
|
||||
3. Requires `blk0001.dat` to exist after extraction
|
||||
4. Looks for `txleveldb/` and `snapshot.manifest`
|
||||
5. Keeps `txleveldb/` only if `snapshot.manifest` passes verification
|
||||
6. Deletes `txleveldb/` if verification fails, then rebuilds from `blk0001.dat`
|
||||
7. Always deletes `database/` from the extracted snapshot
|
||||
|
||||
The verification rules are strict:
|
||||
|
||||
- `format` must be `1`
|
||||
- `network` must be `main` on mainnet
|
||||
- `dbversion` must be `70509`
|
||||
- `height` and `hash` must exactly match a hardcoded checkpoint
|
||||
|
||||
If any of those checks fail, the wallet throws away the shipped `txleveldb/`.
|
||||
|
||||
## Current Hardcoded Mainnet Checkpoint
|
||||
|
||||
As of the current codebase, the latest hardcoded mainnet checkpoint is:
|
||||
|
||||
- Height: `2186940`
|
||||
- Hash: `bd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0`
|
||||
|
||||
OpenClaw must not generate a manifest with an arbitrary tip hash. The manifest only survives if it matches a hardcoded checkpoint from `src/checkpoints.cpp`.
|
||||
|
||||
## Important Limitation
|
||||
|
||||
If the live chain tip is past the latest hardcoded checkpoint, OpenClaw has two valid options:
|
||||
|
||||
1. Publish a snapshot taken exactly at the latest hardcoded checkpoint
|
||||
2. Publish `blk0001.dat` only, without `txleveldb/`, and let clients rebuild locally
|
||||
|
||||
OpenClaw must not publish a `snapshot.manifest` for a height/hash that is not compiled into the wallet.
|
||||
|
||||
## Files OpenClaw Should Publish
|
||||
|
||||
The preferred `bootstrap.tar.gz` should contain:
|
||||
|
||||
- `blk0001.dat`
|
||||
- `txleveldb/`
|
||||
- `snapshot.manifest`
|
||||
- optionally `peers.dat`
|
||||
|
||||
It must not contain:
|
||||
|
||||
- `wallet.dat`
|
||||
- `database/`
|
||||
- `.lock`
|
||||
- pid files
|
||||
- logs
|
||||
- Tor state
|
||||
|
||||
Legacy fallback files should still exist on the web root:
|
||||
|
||||
- `blk0001.dat`
|
||||
- `filelist.txt`
|
||||
|
||||
## Requirements For The Source Node
|
||||
|
||||
Before building a snapshot, the source node should be:
|
||||
|
||||
- fully synced
|
||||
- cleanly shut down before copying files
|
||||
- built from the same code/version expected by clients
|
||||
- using the same LevelDB schema as the client (`DATABASE_VERSION=70509`)
|
||||
|
||||
Recommended node config for the source snapshot node:
|
||||
|
||||
```ini
|
||||
txindex=1
|
||||
addressindex=1
|
||||
daemon=1
|
||||
server=1
|
||||
```
|
||||
|
||||
`addressindex=1` is recommended so clients that enable address index can benefit from faster indexed wallet rescans and address RPCs immediately.
|
||||
|
||||
## OpenClaw Workflow
|
||||
|
||||
### Step 1: Decide Whether A Prebuilt Index Is Allowed
|
||||
|
||||
OpenClaw must first decide whether it can ship `txleveldb/`.
|
||||
|
||||
Rules:
|
||||
|
||||
- If the snapshot node is exactly at checkpoint `2186940`, shipping `txleveldb/` is allowed
|
||||
- If the snapshot node is above `2186940` and the code has not been updated with a newer checkpoint, do not ship `txleveldb/`
|
||||
- In that case, publish a blocks-only bootstrap instead
|
||||
|
||||
### Step 2: Stop The Source Node Cleanly
|
||||
|
||||
Never copy a live LevelDB directory.
|
||||
|
||||
```bash
|
||||
trianglesd stop
|
||||
sleep 10
|
||||
pgrep -af trianglesd || true
|
||||
```
|
||||
|
||||
OpenClaw should confirm the daemon is fully stopped before copying `txleveldb/`.
|
||||
|
||||
### Step 3: Create A Staging Directory
|
||||
|
||||
```bash
|
||||
rm -rf /tmp/triangles-bootstrap-stage
|
||||
mkdir -p /tmp/triangles-bootstrap-stage
|
||||
```
|
||||
|
||||
### Step 4: Copy Snapshot Files
|
||||
|
||||
For a verified snapshot:
|
||||
|
||||
```bash
|
||||
cp ~/.triangles/blk0001.dat /tmp/triangles-bootstrap-stage/
|
||||
cp -a ~/.triangles/txleveldb /tmp/triangles-bootstrap-stage/
|
||||
test -f ~/.triangles/peers.dat && cp ~/.triangles/peers.dat /tmp/triangles-bootstrap-stage/
|
||||
```
|
||||
|
||||
Do not copy:
|
||||
|
||||
```bash
|
||||
rm -rf /tmp/triangles-bootstrap-stage/database
|
||||
rm -f /tmp/triangles-bootstrap-stage/wallet.dat
|
||||
rm -f /tmp/triangles-bootstrap-stage/.lock
|
||||
rm -f /tmp/triangles-bootstrap-stage/*.pid
|
||||
rm -f /tmp/triangles-bootstrap-stage/debug.log
|
||||
```
|
||||
|
||||
### Step 5: Write `snapshot.manifest`
|
||||
|
||||
If OpenClaw is publishing a verified prebuilt index, write:
|
||||
|
||||
```bash
|
||||
cat > /tmp/triangles-bootstrap-stage/snapshot.manifest << 'EOF'
|
||||
format=1
|
||||
network=main
|
||||
height=2186940
|
||||
hash=bd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0
|
||||
dbversion=70509
|
||||
EOF
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `hash` must not include `0x`
|
||||
- `network` must be `main`
|
||||
- `dbversion` must be `70509`
|
||||
- If OpenClaw is publishing blocks-only bootstrap, it should omit `snapshot.manifest` entirely
|
||||
|
||||
### Step 6: Build The Tarball
|
||||
|
||||
```bash
|
||||
cd /tmp/triangles-bootstrap-stage
|
||||
tar czf /tmp/bootstrap.tar.gz .
|
||||
```
|
||||
|
||||
### Step 7: Publish To The Existing Bootstrap Server
|
||||
|
||||
This guide assumes the existing nginx root is:
|
||||
|
||||
- `/var/www/triangles-bootstrap`
|
||||
|
||||
Publish the preferred tarball and the legacy fallback files:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/triangles-bootstrap
|
||||
sudo mv /tmp/bootstrap.tar.gz /var/www/triangles-bootstrap/bootstrap.tar.gz
|
||||
sudo cp ~/.triangles/blk0001.dat /var/www/triangles-bootstrap/blk0001.dat
|
||||
printf "blk0001.dat\n" | sudo tee /var/www/triangles-bootstrap/filelist.txt > /dev/null
|
||||
sudo chown -R www-data:www-data /var/www/triangles-bootstrap
|
||||
```
|
||||
|
||||
If OpenClaw is publishing a blocks-only bootstrap, the commands are the same except the tarball should contain only `blk0001.dat` and optional `peers.dat`.
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Before marking the update complete, OpenClaw should verify:
|
||||
|
||||
### Tarball contents
|
||||
|
||||
```bash
|
||||
tar tzf /var/www/triangles-bootstrap/bootstrap.tar.gz | sort
|
||||
```
|
||||
|
||||
Expected for verified snapshot:
|
||||
|
||||
- `./blk0001.dat`
|
||||
- `./txleveldb/...`
|
||||
- `./snapshot.manifest`
|
||||
|
||||
Expected not to exist:
|
||||
|
||||
- `wallet.dat`
|
||||
- `database/`
|
||||
|
||||
### HTTP responses
|
||||
|
||||
```bash
|
||||
curl -I http://localhost/bootstrap.tar.gz
|
||||
curl -I http://localhost/blk0001.dat
|
||||
curl http://localhost/filelist.txt
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- HTTP `200`
|
||||
- `filelist.txt` contains `blk0001.dat`
|
||||
|
||||
### Manifest sanity
|
||||
|
||||
```bash
|
||||
tar xOf /var/www/triangles-bootstrap/bootstrap.tar.gz ./snapshot.manifest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `format=1`
|
||||
- `network=main`
|
||||
- `height=2186940`
|
||||
- `hash=bd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0`
|
||||
- `dbversion=70509`
|
||||
|
||||
## Fresh-Client Test
|
||||
|
||||
OpenClaw should test the artifact on a clean machine or clean data directory:
|
||||
|
||||
```bash
|
||||
mv ~/.triangles ~/.triangles.backup.$(date +%s)
|
||||
mkdir -p ~/.triangles
|
||||
trianglesd -bootstrap
|
||||
```
|
||||
|
||||
Then inspect startup logs.
|
||||
|
||||
Successful verified snapshot behavior should include:
|
||||
|
||||
- snapshot downloaded
|
||||
- `snapshot.manifest found`
|
||||
- `manifest verified - keeping pre-built index`
|
||||
- no message about removing extracted `txleveldb/`
|
||||
|
||||
Failure behavior will include:
|
||||
|
||||
- manifest parse or verification failure
|
||||
- `removing extracted txleveldb/`
|
||||
- slow rebuild from `blk0001.dat`
|
||||
|
||||
## Safe Publish Procedure
|
||||
|
||||
OpenClaw should use this order:
|
||||
|
||||
1. Build snapshot in `/tmp`
|
||||
2. Validate tarball contents
|
||||
3. Replace `/var/www/triangles-bootstrap/bootstrap.tar.gz`
|
||||
4. Replace `/var/www/triangles-bootstrap/blk0001.dat`
|
||||
5. Replace `/var/www/triangles-bootstrap/filelist.txt`
|
||||
6. Confirm HTTP `200`
|
||||
|
||||
This avoids serving a half-written tarball.
|
||||
|
||||
## Example Bot Prompt
|
||||
|
||||
Use this exact tasking for OpenClaw:
|
||||
|
||||
```text
|
||||
Update the existing Triangles bootstrap server on bootstrap.cryptographic-triangles.org.
|
||||
|
||||
Rules:
|
||||
- Build the snapshot from a cleanly stopped source node
|
||||
- If the source node is exactly at hardcoded checkpoint 2186940 / bd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0, publish a verified snapshot containing blk0001.dat, txleveldb/, and snapshot.manifest
|
||||
- If the source node is above the latest hardcoded checkpoint, publish a blocks-only bootstrap and do not ship txleveldb/
|
||||
- Do not ship wallet.dat, database/, .lock, pid files, logs, or Tor state
|
||||
- Publish bootstrap.tar.gz, blk0001.dat, and filelist.txt to /var/www/triangles-bootstrap
|
||||
- Verify curl HTTP 200 for bootstrap.tar.gz and blk0001.dat
|
||||
- Report the tarball contents and whether the snapshot is verified or blocks-only
|
||||
```
|
||||
|
||||
## Recommended Next Improvement
|
||||
|
||||
This workflow will stay constrained until the next checkpoint is updated in `src/checkpoints.cpp`.
|
||||
|
||||
If you want OpenClaw to keep shipping prebuilt `txleveldb/` snapshots as the chain advances, the software needs periodic checkpoint updates. Without that, the verified snapshot path will stop at the latest compiled checkpoint and clients will fall back to rebuilds.
|
||||
@@ -1,123 +0,0 @@
|
||||
# TODO/FIXME Documentation
|
||||
|
||||
Detailed context for each TODO/FIXME in the codebase.
|
||||
|
||||
## Critical (Needs Attention)
|
||||
|
||||
### src/rpcmining.cpp:263 - Thread Safety Issue
|
||||
```cpp
|
||||
static mapNewBlock_t mapNewBlock; // FIXME: thread safety
|
||||
```
|
||||
**Issue:** Static variable accessed by multiple RPC threads without mutex protection.
|
||||
**Impact:** Potential race condition in getwork RPC (used for mining).
|
||||
**Status:** Low priority - PoW mining ended at block 9000, this code path rarely used.
|
||||
**Fix:** Add std::mutex and lock_guard if getwork usage increases.
|
||||
|
||||
### src/qt/walletmodel.cpp:249 - Collision Risk
|
||||
```cpp
|
||||
if((total + nFeeRequired) > nBalance) // FIXME: could cause collisions in the future
|
||||
```
|
||||
**Issue:** Balance check may have edge case causing transaction collisions.
|
||||
**Context:** In createTransaction fee calculation loop.
|
||||
**Status:** Needs investigation - unclear what "collisions" means here.
|
||||
**Fix:** Review Bitcoin Core's current implementation of this logic.
|
||||
|
||||
### src/smessage.cpp - File Size Limits
|
||||
```cpp
|
||||
// Lines 863, 2219, 2373: "TODO files must be split if > 2GB"
|
||||
```
|
||||
**Issue:** Secure message storage files not split when exceeding 2GB.
|
||||
**Impact:** May fail on 32-bit systems or with large message volumes.
|
||||
**Status:** Low priority - unlikely to reach 2GB in practice.
|
||||
**Fix:** Implement file rotation when approaching 2GB limit.
|
||||
|
||||
## Medium Priority (Encapsulation/API)
|
||||
|
||||
### src/protocol.h - Make Members Private
|
||||
```cpp
|
||||
// Lines 50, 100, 132: "TODO: make private (improves encapsulation)"
|
||||
```
|
||||
**Issue:** CAddress, CInv, CMessageHeader have public data members.
|
||||
**Impact:** Poor encapsulation, harder to maintain invariants.
|
||||
**Status:** Deferred - would require extensive refactoring.
|
||||
**Fix:** Add getter/setter methods, make members private, update all call sites.
|
||||
|
||||
### src/wallet.h:378 - nOrderPos Calculation
|
||||
```cpp
|
||||
nOrderPos = -1; // TODO: calculate elsewhere
|
||||
```
|
||||
**Issue:** Transaction ordering position calculated in constructor.
|
||||
**Impact:** Minor - works but not ideal separation of concerns.
|
||||
**Status:** Deferred - no functional issue.
|
||||
**Fix:** Move calculation to WalletDB when transaction is added.
|
||||
|
||||
### src/rpcwallet.cpp / src/qt/askpassphrasedialog.cpp - SecureString Conversion
|
||||
**Issue:** Password-handling paths were converting through `.c_str()` because `SecureString`
|
||||
did not have a convenient conversion helper from `std::string`.
|
||||
**Impact:** Unnecessary C-string shims in sensitive code paths.
|
||||
**Status:** Resolved.
|
||||
**Fix:** Added `MakeSecureString(const std::string&)` in `src/allocators.h` and updated
|
||||
the wallet RPC and passphrase dialog call sites to use it directly.
|
||||
|
||||
## Low Priority (Nice-to-Have)
|
||||
|
||||
### src/util.cpp:1322 - Disabled Feature
|
||||
```cpp
|
||||
// TODO: This is currently disabled because it needs to be verified to work
|
||||
```
|
||||
**Context:** File descriptor management code.
|
||||
**Status:** Intentionally disabled pending verification.
|
||||
**Fix:** Test thoroughly, then enable if needed.
|
||||
|
||||
### src/tor/tor_embedded.cpp:209 - Tor Shutdown API
|
||||
```cpp
|
||||
// TODO: Tor 0.4.9+ may add tor_api_shutdown(), use it when available
|
||||
```
|
||||
**Context:** Embedded Tor cleanup.
|
||||
**Status:** Waiting for upstream Tor API.
|
||||
**Fix:** Check Tor 0.4.9+ releases for new API, integrate when stable.
|
||||
|
||||
### src/init.cpp:442 - Sanity Checks
|
||||
```cpp
|
||||
// TODO: remaining sanity checks, see #4081
|
||||
```
|
||||
**Context:** Bitcoin Core issue #4081 - additional startup sanity checks.
|
||||
**Status:** Deferred - core checks already in place.
|
||||
**Fix:** Review Bitcoin Core's current sanity check implementation.
|
||||
|
||||
### src/rpcmining.cpp:232 - DRM Comment
|
||||
```cpp
|
||||
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0]; // FIXME - DRM!
|
||||
```
|
||||
**Issue:** Unclear what "DRM" means here - likely "Data Race Maybe"?
|
||||
**Status:** Needs clarification from original author.
|
||||
**Fix:** Investigate if there's an actual issue, otherwise remove comment.
|
||||
|
||||
## Deferred (External/Low Impact)
|
||||
|
||||
### LevelDB TODOs (src/leveldb/*)
|
||||
**Status:** Upstream LevelDB issues - don't modify embedded library.
|
||||
**Action:** None - track upstream LevelDB project.
|
||||
|
||||
### Qt TODOs (src/qt/*)
|
||||
**Status:** UI improvements, not critical.
|
||||
**Action:** Track as nice-to-have enhancements.
|
||||
|
||||
### Secure Message TODOs (src/smessage.cpp)
|
||||
Multiple minor improvements suggested:
|
||||
- Include hash in certain operations
|
||||
- Improve thread shutdown
|
||||
- Set default recv/recvAnon behavior
|
||||
- Update outbox after PoW completes
|
||||
|
||||
**Status:** Non-critical enhancements.
|
||||
**Action:** Consider for future encrypted messaging upgrades.
|
||||
|
||||
## Summary
|
||||
|
||||
**Critical:** 3 items (thread safety, balance collision, file limits)
|
||||
**Medium:** 6 items (encapsulation, SecureString)
|
||||
**Low:** 5 items (disabled features, upstream APIs)
|
||||
**Deferred:** ~24 items (external libs, minor enhancements)
|
||||
|
||||
**Recommendation:** Focus on documenting critical items in code comments, defer fixes until specific issues arise.
|
||||
@@ -0,0 +1,281 @@
|
||||
# Triangles (TRI) RPC Command Reference
|
||||
|
||||
This document describes every RPC command available in the Triangles daemon (`trianglesd`) and Qt wallet. Connect via JSON-RPC on port **19112** (default). All commands can also be run from the Qt wallet's debug console.
|
||||
|
||||
Triangles is a Tor-only PoS cryptocurrency. PoW ended at block 9000; from block 9001 onward the chain is pure Proof-of-Stake with 33% annual interest (coin-age based). Block time is 2 minutes. Max supply is 2,222,222 TRI.
|
||||
|
||||
---
|
||||
|
||||
## Server Control
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `help` | `[command]` | List all commands, or get detailed help for a specific command. |
|
||||
| `stop` | | Shut down the daemon. |
|
||||
|
||||
---
|
||||
|
||||
## Blockchain
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `getbestblockhash` | | Returns the hash of the tip of the best chain. |
|
||||
| `getblockcount` | | Returns the current block height. |
|
||||
| `getblockhash` | `<index>` | Returns the block hash at the given height. |
|
||||
| `getblock` | `<hash> [txinfo]` | Returns block details for the given hash. Set `txinfo=true` for full transaction data. |
|
||||
| `getblockbynumber` | `<number> [txinfo]` | Same as `getblock` but accepts a height instead of a hash. |
|
||||
| `getblockheader` | `<hash> [verbose=true]` | Returns block header data. If verbose is false, returns hex-encoded header. |
|
||||
| `getblockchaininfo` | | Returns chain state info: chain name, block height, best hash, difficulty, etc. |
|
||||
| `getdifficulty` | | Returns current PoW and PoS difficulty values. |
|
||||
| `gettxoutsetinfo` | | Returns statistics about the UTXO set (total txouts, size, etc.). |
|
||||
| `getrawmempool` | | Returns all transaction IDs currently in the mempool. |
|
||||
| `getcheckpoint` | | Returns info about the current synchronized checkpoint. |
|
||||
| `getchaintips` | | Returns info about all known chain tips (forks). |
|
||||
| `invalidateblock` | `<hash>` | Permanently marks a block as invalid and rewinds the chain past it. |
|
||||
| `reconsiderblock` | `<hash>` | Removes the invalid mark from a previously invalidated block. |
|
||||
| `recalculatesupply` | | Recalculates money supply by summing all UTXOs. Updates the stored value at the chain tip and persists to disk. Returns old/new supply and difference. |
|
||||
| `settxfee` | `<amount>` | Sets the transaction fee per kB. Amount is rounded to nearest 0.01. |
|
||||
| `estimatefee` | `<nblocks>` | Estimates the fee per kB needed for confirmation within `nblocks` blocks. |
|
||||
|
||||
---
|
||||
|
||||
## Address Index
|
||||
|
||||
These commands query the address index. The daemon must be running with `-addressindex=1`.
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `getaddressbalance` | `{"addresses":["addr",...]}` | Returns confirmed balance for the given address(es). |
|
||||
| `getaddressutxos` | `{"addresses":["addr",...]}` | Returns all unspent outputs for the given address(es). |
|
||||
| `getaddresstxids` | `{"addresses":["addr",...], "start":n, "end":n}` | Returns transaction IDs for the given address(es), optionally filtered by block range. |
|
||||
|
||||
---
|
||||
|
||||
## Mining & Staking
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `getmininginfo` | | Returns mining-related info: height, difficulty, network hashrate, etc. |
|
||||
| `getstakinginfo` | | Returns staking-related info: whether staking is active, weight, expected time to stake, etc. |
|
||||
| `getsubsidy` | `[nTarget]` | Returns the PoW subsidy value for the given target height (historical reference only since PoW ended at block 9000). |
|
||||
|
||||
---
|
||||
|
||||
## Network
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `getconnectioncount` | | Returns the number of peer connections. |
|
||||
| `getpeerinfo` | | Returns detailed info about each connected peer (address, version, ping time, etc.). |
|
||||
| `getnetworkinfo` | | Returns P2P network state: version, protocol, peer mix, connections, relay fee, etc. |
|
||||
| `getseedlist` | | Returns the list of configured seed nodes. |
|
||||
| `addnode` | `<node> <add\|remove\|onetry>` | Add or remove a node from the manual peer list, or try connecting once. For Tor nodes use the `.onion` address. |
|
||||
| `disconnectnode` | `<node>` | Immediately disconnects from the specified peer. |
|
||||
| `sendalert` | `<message> <privatekey> <minver> <maxver> <priority> <id> [cancelupto]` | Broadcasts a network alert (requires the alert master private key). |
|
||||
|
||||
---
|
||||
|
||||
## Wallet — General
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `getinfo` | | Returns general info: version, balance, stake, block height, connections, etc. |
|
||||
| `getwalletinfo` | | Returns wallet-specific info: balance, unconfirmed, immature, txcount, keypoolsize, etc. |
|
||||
| `getbalance` | `[account] [minconf=1]` | Returns total available balance (optionally for a specific account). |
|
||||
| `checkwallet` | | Checks wallet database for consistency errors. |
|
||||
| `repairwallet` | | Attempts to repair the wallet database. |
|
||||
| `resendtx` | | Re-broadcasts all unconfirmed wallet transactions. |
|
||||
|
||||
---
|
||||
|
||||
## Wallet — Addresses & Accounts
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `getnewaddress` | `[account]` | Generates a new receiving address (optionally assigned to an account). |
|
||||
| `getnewpubkey` | `[account]` | Returns a new public key for the wallet. |
|
||||
| `getaccountaddress` | `<account>` | Returns the current receiving address for the given account. |
|
||||
| `setaccount` | `<address> <account>` | Assigns an address to the given account label. |
|
||||
| `getaccount` | `<address>` | Returns the account label for the given address. |
|
||||
| `getaddressesbyaccount` | `<account>` | Returns all addresses assigned to the given account. |
|
||||
| `listaddressgroupings` | | Returns addresses grouped by common ownership (based on transaction history). |
|
||||
| `validateaddress` | `<address>` | Validates a Triangles address and returns info (ismine, account, pubkey, etc.). |
|
||||
| `validatepubkey` | `<pubkey>` | Validates a Triangles public key. |
|
||||
| `listaccounts` | `[minconf=1]` | Returns all account names and their balances. |
|
||||
|
||||
---
|
||||
|
||||
## Wallet — Sending
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `sendtoaddress` | `<address> <amount> [comment] [comment-to]` | Sends TRI to an address. Returns the transaction ID. |
|
||||
| `sendfrom` | `<fromaccount> <address> <amount> [minconf=1] [comment] [comment-to]` | Sends TRI from a specific account. |
|
||||
| `sendmany` | `<fromaccount> {"addr":amount,...} [minconf=1] [comment]` | Sends TRI to multiple addresses in a single transaction. |
|
||||
| `move` | `<fromaccount> <toaccount> <amount> [minconf=1] [comment]` | Moves funds between accounts (internal bookkeeping only, no on-chain tx). |
|
||||
|
||||
---
|
||||
|
||||
## Wallet — Transaction History
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `listtransactions` | `[account] [count=10] [from=0]` | Returns the most recent transactions (optionally filtered by account). |
|
||||
| `listsinceblock` | `[blockhash] [target-confirmations]` | Returns all transactions since the given block. |
|
||||
| `gettransaction` | `<txid>` | Returns detailed info about a wallet transaction. |
|
||||
| `getreceivedbyaddress` | `<address> [minconf=1]` | Returns total amount received by an address. |
|
||||
| `getreceivedbyaccount` | `<account> [minconf=1]` | Returns total amount received by an account. |
|
||||
| `listreceivedbyaddress` | `[minconf=1] [includeempty=false]` | Returns amounts received for each address. |
|
||||
| `listreceivedbyaccount` | `[minconf=1] [includeempty=false]` | Returns amounts received for each account. |
|
||||
|
||||
---
|
||||
|
||||
## Wallet — Staking Control
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `reservebalance` | `[reserve] [amount]` | Show or set a reserve balance that will not be used for staking. `reserve` is true/false, `amount` is the TRI to reserve. |
|
||||
|
||||
---
|
||||
|
||||
## Wallet — Security
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `encryptwallet` | `<passphrase>` | Encrypts the wallet with the given passphrase. **This shuts down the daemon.** The wallet must be re-started and unlocked afterward. |
|
||||
| `walletpassphrase` | `<passphrase> <timeout> [stakingonly]` | Unlocks the wallet for `timeout` seconds. Set `stakingonly=true` to allow staking but prevent sending. |
|
||||
| `walletpassphrasechange` | `<oldpassphrase> <newpassphrase>` | Changes the wallet encryption passphrase. |
|
||||
| `walletlock` | | Immediately locks the wallet (removes decryption key from memory). |
|
||||
| `keypoolrefill` | `[new-size]` | Tops up the pre-generated key pool. |
|
||||
| `makekeypair` | `[prefix]` | Generates a new public/private keypair (not added to wallet). |
|
||||
|
||||
---
|
||||
|
||||
## Wallet — Backup & Import
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `backupwallet` | `<destination>` | Copies `wallet.dat` to the given file path. |
|
||||
| `dumpwallet` | `<filename>` | Exports all wallet private keys to a plaintext file. |
|
||||
| `dumpprivkey` | `<address>` | Returns the private key (WIF format) for the given address. |
|
||||
| `importwallet` | `<filename>` | Imports keys from a wallet dump file. |
|
||||
| `importprivkey` | `<privkey> [label]` | Imports a single private key (WIF format) with optional label. |
|
||||
|
||||
---
|
||||
|
||||
## Wallet — Multisig
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `addmultisigaddress` | `<nrequired> ["key",...] [account]` | Creates an M-of-N multisig address. `nrequired` is the number of signatures needed. |
|
||||
| `addredeemscript` | `<redeemScript> [account]` | Adds a P2SH redeem script to the wallet. |
|
||||
|
||||
---
|
||||
|
||||
## Wallet — Message Signing
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `signmessage` | `<address> <message>` | Signs a message with the private key of the given address. |
|
||||
| `verifymessage` | `<address> <signature> <message>` | Verifies a signed message. Returns true/false. |
|
||||
|
||||
---
|
||||
|
||||
## Raw Transactions
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `listunspent` | `[minconf=1] [maxconf=9999999] ["addr",...]` | Returns unspent transaction outputs, optionally filtered by address and confirmation count. |
|
||||
| `createrawtransaction` | `[{"txid":"id","vout":n},...] {"addr":amount,...}` | Creates an unsigned raw transaction from the given inputs and outputs. |
|
||||
| `decoderawtransaction` | `<hex>` | Decodes a raw transaction hex string into a JSON object. |
|
||||
| `decodescript` | `<hex>` | Decodes a hex-encoded script into human-readable form. |
|
||||
| `signrawtransaction` | `<hex> [prevtxs] [privkeys] [sighashtype="ALL"]` | Signs a raw transaction. Can provide previous tx outputs and private keys for offline signing. |
|
||||
| `sendrawtransaction` | `<hex>` | Broadcasts a signed raw transaction to the network. Returns the txid. |
|
||||
| `getrawtransaction` | `<txid> [verbose=0]` | Returns raw transaction data. Set verbose=1 for decoded JSON output. |
|
||||
|
||||
---
|
||||
|
||||
## Secure Messaging (SMSG)
|
||||
|
||||
Triangles has a built-in encrypted peer-to-peer messaging system. Messages are stored in a DHT-like bucket system and relayed through the network.
|
||||
|
||||
| Command | Parameters | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `smsgenable` | | Enables the secure messaging system. |
|
||||
| `smsgdisable` | | Disables the secure messaging system. |
|
||||
| `smsgoptions` | `[list\|set <optname> <value>]` | View or change secure messaging options. |
|
||||
| `smsglocalkeys` | `[whitelist\|all\|wallet\|recv +/- <addr>\|anon +/- <addr>]` | Manage which local keys participate in secure messaging. |
|
||||
| `smsgaddkey` | `<address> <pubkey>` | Adds someone's public key so you can send them encrypted messages. |
|
||||
| `smsggetpubkey` | `<address>` | Retrieves the public key for an address (needed to send messages to it). |
|
||||
| `smsgsend` | `<fromAddr> <toAddr> <message>` | Sends an encrypted message from one of your addresses to a recipient. |
|
||||
| `smsgsendanon` | `<toAddr> <message>` | Sends an anonymous encrypted message (no sender address attached). |
|
||||
| `smsginbox` | `[all\|unread\|clear]` | View received secure messages. Default shows unread. |
|
||||
| `smsgoutbox` | `[all\|clear]` | View sent secure messages. |
|
||||
| `smsgscanchain` | | Scans the blockchain for secure message public keys. |
|
||||
| `smsgscanbuckets` | | Scans stored message buckets for messages addressed to your keys. |
|
||||
| `smsgbuckets` | `[stats\|dump]` | View secure message bucket statistics or dump contents. |
|
||||
| `smsgbroadcast` | `<fromAddr> <message>` | Broadcasts a message to all SMSG participants (not encrypted to a single recipient). |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference — Common Tasks
|
||||
|
||||
**Check node status:**
|
||||
```
|
||||
getinfo
|
||||
getblockcount
|
||||
getconnectioncount
|
||||
getstakinginfo
|
||||
```
|
||||
|
||||
**Check balance and transactions:**
|
||||
```
|
||||
getbalance
|
||||
listtransactions
|
||||
```
|
||||
|
||||
**Send coins:**
|
||||
```
|
||||
walletpassphrase "yourpassphrase" 60
|
||||
sendtoaddress "TRIaddress" 100
|
||||
walletlock
|
||||
```
|
||||
|
||||
**Unlock for staking only:**
|
||||
```
|
||||
walletpassphrase "yourpassphrase" 999999999 true
|
||||
```
|
||||
|
||||
**Add a peer manually (Tor .onion):**
|
||||
```
|
||||
addnode "abcdef1234567890.onion" "add"
|
||||
```
|
||||
|
||||
**Export/import a private key:**
|
||||
```
|
||||
dumpprivkey "TRIaddress"
|
||||
importprivkey "5KPrivKeyHere" "mylabel"
|
||||
```
|
||||
|
||||
**Fix incorrect money supply display:**
|
||||
```
|
||||
recalculatesupply
|
||||
```
|
||||
|
||||
**Full reindex (rebuild block index from raw data):**
|
||||
```
|
||||
trianglesd -reindex
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Connection Info
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Default RPC port | 19112 |
|
||||
| Default P2P port | 24112 |
|
||||
| Config file (Windows) | `%APPDATA%\triangles\triangles.conf` |
|
||||
| Config file (Linux) | `~/.triangles/triangles.conf` |
|
||||
| Protocol version | 70205 |
|
||||
| Network | Tor-only |
|
||||
@@ -9,6 +9,20 @@ if(NOT TARGET leveldb_lib)
|
||||
add_subdirectory("${LEVELDB_SOURCE_DIR}" "${LEVELDB_BINARY_DIR}")
|
||||
endif()
|
||||
|
||||
# Pin bundled LevelDB to C++17. It only needs C++11 (declared via its own
|
||||
# target_compile_features) but inherits CMAKE_CXX_STANDARD=20 from the
|
||||
# top-level project, where some of its atomic-enum syntax
|
||||
# (std::memory_order::memory_order_relaxed) becomes a hard error.
|
||||
foreach(_leveldb_target leveldb_lib leveldb_memenv)
|
||||
if(TARGET ${_leveldb_target})
|
||||
set_target_properties(${_leveldb_target} PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS OFF
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(NOT TARGET build_leveldb)
|
||||
add_custom_target(build_leveldb DEPENDS leveldb_lib leveldb_memenv)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# Chain DB benchmark harness
|
||||
|
||||
Measures `FastImportBlockFile()` speed under each chain-DB backend
|
||||
(LevelDB vs RocksDB) using a user-supplied `blk0001.dat` block stream.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A `trianglesd` binary (RocksDB is now a hard build dep, both backends are
|
||||
always available):
|
||||
```
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_QT=OFF \
|
||||
-DBUILD_DAEMON=ON
|
||||
cmake --build build
|
||||
```
|
||||
- An `blk0001.dat` file (old-style block stream). If you have a synced
|
||||
node, copy `~/.triangles/blk0001.dat` (Linux) or `%APPDATA%\triangles\blk0001.dat` (Windows).
|
||||
- Free disk space: ~3× the size of `blk0001.dat` per backend run
|
||||
(raw blocks + chain DB index + working space).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
contrib/bench/bench-chaindb.sh \
|
||||
--binary=$(pwd)/build/bin/trianglesd \
|
||||
--bootstrap=/path/to/blk0001.dat
|
||||
```
|
||||
|
||||
Runs each backend in turn, appends a CSV row to `./bench-results.csv`,
|
||||
and prints a summary to stdout. Default `--dbcache=2048` (MB).
|
||||
|
||||
### Options
|
||||
|
||||
| Flag | Default | Notes |
|
||||
| --- | --- | --- |
|
||||
| `--binary=PATH` | (required) | Path to `trianglesd` |
|
||||
| `--bootstrap=PATH` | (required) | Path to `blk0001.dat` |
|
||||
| `--backends=LIST` | `leveldb,rocksdb` | Comma-separated subset |
|
||||
| `--workdir=DIR` | `/tmp/triangles-bench-XXXXXX` | Per-backend datadirs go here |
|
||||
| `--dbcache=MB` | `2048` | Chain DB cache size |
|
||||
| `--results-csv=FILE` | `./bench-results.csv` | Appended to |
|
||||
| `--keep-datadirs` | off | Preserve datadirs after run for inspection |
|
||||
| `--rpc-port=BASE` | `19112` | Each backend uses `BASE+offset` |
|
||||
|
||||
## What it measures
|
||||
|
||||
| Column | Source |
|
||||
| --- | --- |
|
||||
| `wall_ms` | The daemon's own log line: `FastImportBlockFile: indexed N blocks in Mms` |
|
||||
| `peak_rss_kb` | `ps -o rss=` sampled once per second |
|
||||
| `datadir_bytes` | `du -sb` of the working datadir (includes `blk0001.dat`) |
|
||||
| `blocks_indexed` | Parsed from the same log line |
|
||||
|
||||
## What it does not measure
|
||||
|
||||
- Network IBD (peer fetch, header sync) — this is pure DB ingest.
|
||||
- UTXO snapshot load — `LoadSnapshot` is currently rocksdb-guarded
|
||||
(see `src/utxosnapshot.cpp`); will be unblocked when LevelDB is retired.
|
||||
- Reorg cost — separate test, not yet implemented.
|
||||
- Disk I/O bytes (read/written) — could be added with `iostat` integration.
|
||||
|
||||
## Interpreting results
|
||||
|
||||
A meaningful comparison requires both rows to have run on the same machine
|
||||
with the same `blk0001.dat`. The `host` column makes mixing runs across
|
||||
machines visible in the CSV.
|
||||
|
||||
Backend-relevant size comparisons should subtract `bootstrap_size_bytes`
|
||||
from `datadir_bytes` to isolate the chain DB tree.
|
||||
|
||||
## One-liners
|
||||
|
||||
```bash
|
||||
# LevelDB only
|
||||
./bench-chaindb.sh --binary=... --bootstrap=... --backends=leveldb
|
||||
|
||||
# Compare 2GB vs 4GB cache on RocksDB
|
||||
./bench-chaindb.sh --binary=... --bootstrap=... --backends=rocksdb --dbcache=2048
|
||||
./bench-chaindb.sh --binary=... --bootstrap=... --backends=rocksdb --dbcache=4096
|
||||
|
||||
# Keep the datadirs for poking around afterwards
|
||||
./bench-chaindb.sh --binary=... --bootstrap=... --keep-datadirs
|
||||
```
|
||||
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env bash
|
||||
# Benchmark FastImportBlockFile() speed across chain-DB backends.
|
||||
#
|
||||
# Reads a user-supplied blk0001.dat (old-style block stream) and times the
|
||||
# full block-index rebuild under each backend. Output: a CSV row per backend
|
||||
# with wall time, peak RSS, and resulting datadir size on disk.
|
||||
#
|
||||
# Usage:
|
||||
# ./bench-chaindb.sh \
|
||||
# --binary=/path/to/trianglesd \
|
||||
# --bootstrap=/path/to/blk0001.dat \
|
||||
# [--backends=leveldb,rocksdb] default: both
|
||||
# [--workdir=/tmp/triangles-bench] parent dir for per-backend datadirs
|
||||
# [--dbcache=2048] in MB
|
||||
# [--results-csv=./bench-results.csv]
|
||||
# [--keep-datadirs] preserve datadirs after run
|
||||
# [--rpc-port=BASE] default 19112; each run uses BASE+offset
|
||||
#
|
||||
# Notes:
|
||||
# - RocksDB is a hard build dep, so any current trianglesd has both backends.
|
||||
# - This script does not assume Tor is configured. It launches with -nolisten
|
||||
# and -connect=0 to keep the run network-isolated.
|
||||
# - Wall time comes from the daemon's own perf log line:
|
||||
# "FastImportBlockFile: indexed N blocks in Mms"
|
||||
# - Peak RSS is sampled via `ps -o rss=` once a second.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ────────────────────────────────────────────────────────────────
|
||||
BINARY=""
|
||||
BOOTSTRAP=""
|
||||
BACKENDS="leveldb,rocksdb"
|
||||
WORKDIR=""
|
||||
DBCACHE=2048
|
||||
RESULTS_CSV="./bench-results.csv"
|
||||
KEEP=0
|
||||
RPC_BASE=19112
|
||||
|
||||
# ── Arg parsing ─────────────────────────────────────────────────────────────
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--binary=*) BINARY="${arg#*=}" ;;
|
||||
--bootstrap=*) BOOTSTRAP="${arg#*=}" ;;
|
||||
--backends=*) BACKENDS="${arg#*=}" ;;
|
||||
--workdir=*) WORKDIR="${arg#*=}" ;;
|
||||
--dbcache=*) DBCACHE="${arg#*=}" ;;
|
||||
--results-csv=*) RESULTS_CSV="${arg#*=}" ;;
|
||||
--keep-datadirs) KEEP=1 ;;
|
||||
--rpc-port=*) RPC_BASE="${arg#*=}" ;;
|
||||
-h|--help)
|
||||
sed -n '2,28p' "$0" | sed 's/^# \?//'
|
||||
exit 0 ;;
|
||||
*)
|
||||
echo "Unknown argument: $arg" >&2
|
||||
exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$BINARY" ] || { echo "--binary is required" >&2; exit 2; }
|
||||
[ -n "$BOOTSTRAP" ] || { echo "--bootstrap is required" >&2; exit 2; }
|
||||
[ -x "$BINARY" ] || { echo "Binary not executable: $BINARY" >&2; exit 2; }
|
||||
[ -f "$BOOTSTRAP" ] || { echo "Bootstrap file not found: $BOOTSTRAP" >&2; exit 2; }
|
||||
|
||||
if [ -z "$WORKDIR" ]; then
|
||||
WORKDIR="$(mktemp -d -t triangles-bench-XXXXXX)"
|
||||
fi
|
||||
mkdir -p "$WORKDIR"
|
||||
echo "Workdir: $WORKDIR"
|
||||
|
||||
# ── CSV header (only if file is new) ───────────────────────────────────────
|
||||
if [ ! -f "$RESULTS_CSV" ]; then
|
||||
echo "timestamp,backend,bootstrap_size_bytes,dbcache_mb,blocks_indexed,wall_ms,peak_rss_kb,datadir_bytes,binary,host" > "$RESULTS_CSV"
|
||||
fi
|
||||
|
||||
bootstrap_size="$(stat -c%s "$BOOTSTRAP" 2>/dev/null || stat -f%z "$BOOTSTRAP")"
|
||||
host="$(hostname)"
|
||||
ts_run="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
# ── Per-backend run ─────────────────────────────────────────────────────────
|
||||
run_backend() {
|
||||
local backend="$1"
|
||||
local idx="$2"
|
||||
local datadir="$WORKDIR/$backend"
|
||||
local rpc_port=$((RPC_BASE + idx))
|
||||
local rss_log="$WORKDIR/$backend.rss.log"
|
||||
|
||||
echo
|
||||
echo "════════════════════════════════════════════════════════════════════"
|
||||
echo " Backend: $backend (datadir: $datadir, rpcport: $rpc_port)"
|
||||
echo "════════════════════════════════════════════════════════════════════"
|
||||
|
||||
# Fresh datadir, copy bootstrap into place. FastImportBlockFile() picks
|
||||
# this up automatically when the block index is empty.
|
||||
rm -rf "$datadir"
|
||||
mkdir -p "$datadir"
|
||||
cp "$BOOTSTRAP" "$datadir/blk0001.dat"
|
||||
|
||||
# Minimal config — disable network so we measure only the import path.
|
||||
cat > "$datadir/triangles.conf" <<EOF
|
||||
chaindb=$backend
|
||||
dbcache=$DBCACHE
|
||||
nolisten=1
|
||||
connect=0
|
||||
rpcuser=bench
|
||||
rpcpassword=bench
|
||||
rpcport=$rpc_port
|
||||
debug=1
|
||||
printtoconsole=0
|
||||
EOF
|
||||
|
||||
# Launch in background. -daemon would daemonize but we want to track the
|
||||
# process tree; run in foreground and background it ourselves so we keep
|
||||
# the PID for RSS sampling and clean shutdown.
|
||||
local pid
|
||||
"$BINARY" -datadir="$datadir" -conf="triangles.conf" >"$datadir/stdout.log" 2>&1 &
|
||||
pid=$!
|
||||
echo "Launched $backend (pid $pid)"
|
||||
|
||||
# RSS sampler: log peak every second to a file.
|
||||
(
|
||||
while kill -0 "$pid" 2>/dev/null; do
|
||||
ps -o rss= -p "$pid" 2>/dev/null | tr -d ' ' >> "$rss_log" || true
|
||||
sleep 1
|
||||
done
|
||||
) &
|
||||
local sampler_pid=$!
|
||||
|
||||
# Watch for "FastImportBlockFile: indexed N blocks in Mms" in the daemon's
|
||||
# debug.log, which is the deterministic completion signal.
|
||||
local debug_log="$datadir/debug.log"
|
||||
local wait_start
|
||||
wait_start="$(date +%s)"
|
||||
local timeout_s=86400 # 24 hours hard cap
|
||||
local indexed_line=""
|
||||
while :; do
|
||||
if [ -f "$debug_log" ]; then
|
||||
indexed_line="$(grep -E "FastImportBlockFile: indexed [0-9]+ blocks in [0-9]+ms" "$debug_log" | tail -1 || true)"
|
||||
if [ -n "$indexed_line" ]; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
echo "Daemon exited before completion line appeared. Check $datadir/stdout.log" >&2
|
||||
kill "$sampler_pid" 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
local elapsed=$(( $(date +%s) - wait_start ))
|
||||
if [ "$elapsed" -gt "$timeout_s" ]; then
|
||||
echo "Timeout after ${timeout_s}s without completion line" >&2
|
||||
kill "$pid" 2>/dev/null || true
|
||||
kill "$sampler_pid" 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "Completion: $indexed_line"
|
||||
|
||||
# Parse blocks_indexed and wall_ms from the line.
|
||||
local blocks_indexed wall_ms
|
||||
blocks_indexed="$(echo "$indexed_line" | sed -E 's/.*indexed ([0-9]+) blocks.*/\1/')"
|
||||
wall_ms="$(echo "$indexed_line" | sed -E 's/.*in ([0-9]+)ms.*/\1/')"
|
||||
|
||||
# Stop daemon cleanly via RPC, fall back to SIGTERM.
|
||||
"$BINARY" -datadir="$datadir" -conf="triangles.conf" stop >/dev/null 2>&1 || \
|
||||
kill -TERM "$pid" 2>/dev/null || true
|
||||
|
||||
# Wait up to 60s for clean exit.
|
||||
local stop_wait=0
|
||||
while kill -0 "$pid" 2>/dev/null && [ "$stop_wait" -lt 60 ]; do
|
||||
sleep 1
|
||||
stop_wait=$((stop_wait + 1))
|
||||
done
|
||||
kill -KILL "$pid" 2>/dev/null || true
|
||||
wait "$sampler_pid" 2>/dev/null || true
|
||||
|
||||
# Peak RSS: max of the sampler's recorded values.
|
||||
local peak_rss_kb=0
|
||||
if [ -f "$rss_log" ] && [ -s "$rss_log" ]; then
|
||||
peak_rss_kb="$(sort -nr "$rss_log" | head -1)"
|
||||
fi
|
||||
|
||||
# Datadir size — separate the chain DB from blk0001.dat (which is ~constant
|
||||
# across backends). We report the total datadir size; the consumer can
|
||||
# subtract bootstrap_size_bytes if they want chain-DB-only.
|
||||
local datadir_bytes
|
||||
datadir_bytes="$(du -sb "$datadir" 2>/dev/null | awk '{print $1}' || du -sk "$datadir" | awk '{print $1*1024}')"
|
||||
|
||||
# Append CSV row.
|
||||
echo "$ts_run,$backend,$bootstrap_size,$DBCACHE,$blocks_indexed,$wall_ms,$peak_rss_kb,$datadir_bytes,$BINARY,$host" >> "$RESULTS_CSV"
|
||||
|
||||
# Stdout summary.
|
||||
printf " blocks indexed: %s\n" "$blocks_indexed"
|
||||
printf " wall time: %s ms (%.1f min)\n" "$wall_ms" "$(awk "BEGIN{print $wall_ms/60000}")"
|
||||
printf " peak RSS: %s KB (%.1f GB)\n" "$peak_rss_kb" "$(awk "BEGIN{print $peak_rss_kb/1024/1024}")"
|
||||
printf " datadir size: %s bytes (%.1f GB)\n" "$datadir_bytes" "$(awk "BEGIN{print $datadir_bytes/1024/1024/1024}")"
|
||||
|
||||
# Cleanup unless --keep-datadirs.
|
||||
if [ "$KEEP" -eq 0 ]; then
|
||||
rm -rf "$datadir"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Main loop ──────────────────────────────────────────────────────────────
|
||||
idx=0
|
||||
IFS=',' read -r -a backends_arr <<< "$BACKENDS"
|
||||
for backend in "${backends_arr[@]}"; do
|
||||
case "$backend" in
|
||||
leveldb|rocksdb) ;;
|
||||
*) echo "Unknown backend: $backend" >&2; exit 2 ;;
|
||||
esac
|
||||
run_backend "$backend" "$idx"
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Done. Results appended to $RESULTS_CSV"
|
||||
+27
-8
@@ -38,13 +38,13 @@ target_include_directories(json_compat INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/js
|
||||
# EXCLUDES init.cpp, wallet.cpp (QT_GUI-conditional), noui.cpp (target-specific)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
set(CORE_SOURCES
|
||||
alert.cpp
|
||||
addrman.cpp
|
||||
bootstrap.cpp
|
||||
checkpoints.cpp
|
||||
crypter.cpp
|
||||
crypto_ecdh.cpp
|
||||
crypto_ecdsa.cpp
|
||||
db.cpp
|
||||
irc.cpp
|
||||
key.cpp
|
||||
keystore.cpp
|
||||
main.cpp
|
||||
@@ -62,6 +62,8 @@ set(CORE_SOURCES
|
||||
pbkdf2.cpp
|
||||
scrypt.cpp
|
||||
smessage.cpp
|
||||
syncmanager.cpp
|
||||
chaindb_migrate.cpp
|
||||
tor_embed_hooks.cpp
|
||||
rest.cpp
|
||||
trianglesrpc.cpp
|
||||
@@ -73,8 +75,11 @@ set(CORE_SOURCES
|
||||
rpcrawtransaction.cpp
|
||||
rpcsmessage.cpp
|
||||
zmqpublishnotifier.cpp
|
||||
txdb-base.cpp
|
||||
txdb-factory.cpp
|
||||
txdb-leveldb.cpp
|
||||
utxosnapshot.cpp
|
||||
snapshotnet.cpp
|
||||
lz4/lz4.c
|
||||
tor/onion_v3.cpp
|
||||
tor/tor_process.cpp
|
||||
@@ -96,6 +101,10 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM")
|
||||
list(APPEND CORE_SOURCES scrypt-arm.S)
|
||||
endif()
|
||||
|
||||
# RocksDB chain database backend (always built; see top-level CMakeLists.txt
|
||||
# for the rationale — RocksDB also backs the smessage store).
|
||||
list(APPEND CORE_SOURCES txdb-rocksdb.cpp)
|
||||
|
||||
add_library(triangles_common OBJECT ${CORE_SOURCES})
|
||||
|
||||
target_include_directories(triangles_common PUBLIC
|
||||
@@ -113,7 +122,6 @@ target_link_libraries(triangles_common PUBLIC
|
||||
leveldb_bundled
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
Boost::filesystem
|
||||
Boost::program_options
|
||||
Boost::thread
|
||||
Boost::chrono
|
||||
@@ -143,6 +151,17 @@ if(USE_ZMQ)
|
||||
target_link_libraries(triangles_common PUBLIC PkgConfig::ZMQ)
|
||||
endif()
|
||||
|
||||
# libsecp256k1 (mandatory) — ECDH / ECDSA replacement for OpenSSL EC.
|
||||
# Provided by add_subdirectory(src/secp256k1) in the top-level CMakeLists.
|
||||
target_link_libraries(triangles_common PUBLIC secp256k1)
|
||||
|
||||
# RocksDB (mandatory)
|
||||
if(TARGET RocksDB::rocksdb)
|
||||
target_link_libraries(triangles_common PUBLIC RocksDB::rocksdb)
|
||||
elseif(TARGET PkgConfig::RocksDB)
|
||||
target_link_libraries(triangles_common PUBLIC PkgConfig::RocksDB)
|
||||
endif()
|
||||
|
||||
# Optional: Embedded Tor
|
||||
if(USE_TOR_EMBEDDED)
|
||||
if(TOR_SOURCE_ROOT STREQUAL "")
|
||||
@@ -200,11 +219,11 @@ target_precompile_headers(triangles_common PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<cstring$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<memory$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<functional$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<boost/filesystem.hpp$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<boost/filesystem/fstream.hpp$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<boost/thread.hpp$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<boost/thread/mutex.hpp$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<boost/thread/condition_variable.hpp$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<filesystem$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<fstream$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<thread$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<mutex$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<condition_variable$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<boost/algorithm/string.hpp$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<openssl/sha.h$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<openssl/crypto.h$<ANGLE-R>>"
|
||||
|
||||
+16
-17
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "addrman.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int CAddrInfo::GetTriedBucket(const std::vector<unsigned char> &nKey) const
|
||||
@@ -79,15 +81,14 @@ double CAddrInfo::GetChance(int64_t nNow) const
|
||||
|
||||
CAddrInfo* CAddrMan::Find(const CNetAddr& addr, int *pnId)
|
||||
{
|
||||
std::map<CNetAddr, int>::iterator it = mapAddr.find(addr);
|
||||
auto it = mapAddr.find(addr);
|
||||
if (it == mapAddr.end())
|
||||
return NULL;
|
||||
return nullptr;
|
||||
if (pnId)
|
||||
*pnId = (*it).second;
|
||||
std::map<int, CAddrInfo>::iterator it2 = mapInfo.find((*it).second);
|
||||
if (it2 != mapInfo.end())
|
||||
return &(*it2).second;
|
||||
return NULL;
|
||||
*pnId = it->second;
|
||||
if (auto it2 = mapInfo.find(it->second); it2 != mapInfo.end())
|
||||
return &it2->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CAddrInfo* CAddrMan::Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId)
|
||||
@@ -175,13 +176,13 @@ int CAddrMan::ShrinkNew(int nUBucket)
|
||||
int n[4] = {GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size())};
|
||||
int nI = 0;
|
||||
int nOldest = -1;
|
||||
for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++)
|
||||
for (const auto& elem : vNew)
|
||||
{
|
||||
if (nI == n[0] || nI == n[1] || nI == n[2] || nI == n[3])
|
||||
{
|
||||
assert(nOldest == -1 || mapInfo.count(*it) == 1);
|
||||
if (nOldest == -1 || mapInfo[*it].nTime < mapInfo[nOldest].nTime)
|
||||
nOldest = *it;
|
||||
assert(nOldest == -1 || mapInfo.count(elem) == 1);
|
||||
if (nOldest == -1 || mapInfo[elem].nTime < mapInfo[nOldest].nTime)
|
||||
nOldest = elem;
|
||||
}
|
||||
nI++;
|
||||
}
|
||||
@@ -438,10 +439,8 @@ int CAddrMan::Check_()
|
||||
|
||||
if (vRandom.size() != nTried + nNew) return -7;
|
||||
|
||||
for (std::map<int, CAddrInfo>::iterator it = mapInfo.begin(); it != mapInfo.end(); it++)
|
||||
for (auto& [n, info] : mapInfo)
|
||||
{
|
||||
int n = (*it).first;
|
||||
CAddrInfo &info = (*it).second;
|
||||
if (info.fInTried)
|
||||
{
|
||||
|
||||
@@ -465,10 +464,10 @@ int CAddrMan::Check_()
|
||||
for (int n=0; n<vvTried.size(); n++)
|
||||
{
|
||||
std::vector<int> &vTried = vvTried[n];
|
||||
for (std::vector<int>::iterator it = vTried.begin(); it != vTried.end(); it++)
|
||||
for (const auto& elem : vTried)
|
||||
{
|
||||
if (!setTried.count(*it)) return -11;
|
||||
setTried.erase(*it);
|
||||
if (!setTried.count(elem)) return -11;
|
||||
setTried.erase(elem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-276
@@ -1,276 +0,0 @@
|
||||
//
|
||||
// Alert system
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <map>
|
||||
|
||||
#include "alert.h"
|
||||
#include "key.h"
|
||||
#include "net.h"
|
||||
#include "sync.h"
|
||||
#include "ui_interface.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
map<uint256, CAlert> mapAlerts;
|
||||
CCriticalSection cs_mapAlerts;
|
||||
|
||||
// Alert keys disabled for decentralization - v5 hard fork
|
||||
static const char* pszMainKey = "";
|
||||
|
||||
// TestNet alerts pubKey
|
||||
static const char* pszTestKey = "";
|
||||
|
||||
void CUnsignedAlert::SetNull()
|
||||
{
|
||||
nVersion = 1;
|
||||
nRelayUntil = 0;
|
||||
nExpiration = 0;
|
||||
nID = 0;
|
||||
nCancel = 0;
|
||||
setCancel.clear();
|
||||
nMinVer = 0;
|
||||
nMaxVer = 0;
|
||||
setSubVer.clear();
|
||||
nPriority = 0;
|
||||
|
||||
strComment.clear();
|
||||
strStatusBar.clear();
|
||||
strReserved.clear();
|
||||
}
|
||||
|
||||
std::string CUnsignedAlert::ToString() const
|
||||
{
|
||||
std::string strSetCancel;
|
||||
for (int n : setCancel)
|
||||
strSetCancel += strprintf("%d ", n);
|
||||
std::string strSetSubVer;
|
||||
for (std::string str : setSubVer)
|
||||
strSetSubVer += "\"" + str + "\" ";
|
||||
return strprintf(
|
||||
"CAlert(\n"
|
||||
" nVersion = %d\n"
|
||||
" nRelayUntil = %" PRId64 "\n"
|
||||
" nExpiration = %" PRId64 "\n"
|
||||
" nID = %d\n"
|
||||
" nCancel = %d\n"
|
||||
" setCancel = %s\n"
|
||||
" nMinVer = %d\n"
|
||||
" nMaxVer = %d\n"
|
||||
" setSubVer = %s\n"
|
||||
" nPriority = %d\n"
|
||||
" strComment = \"%s\"\n"
|
||||
" strStatusBar = \"%s\"\n"
|
||||
")\n",
|
||||
nVersion,
|
||||
nRelayUntil,
|
||||
nExpiration,
|
||||
nID,
|
||||
nCancel,
|
||||
strSetCancel.c_str(),
|
||||
nMinVer,
|
||||
nMaxVer,
|
||||
strSetSubVer.c_str(),
|
||||
nPriority,
|
||||
strComment.c_str(),
|
||||
strStatusBar.c_str());
|
||||
}
|
||||
|
||||
void CUnsignedAlert::print() const
|
||||
{
|
||||
printf("%s", ToString().c_str());
|
||||
}
|
||||
|
||||
void CAlert::SetNull()
|
||||
{
|
||||
CUnsignedAlert::SetNull();
|
||||
vchMsg.clear();
|
||||
vchSig.clear();
|
||||
}
|
||||
|
||||
bool CAlert::IsNull() const
|
||||
{
|
||||
return (nExpiration == 0);
|
||||
}
|
||||
|
||||
uint256 CAlert::GetHash() const
|
||||
{
|
||||
return Hash(this->vchMsg.begin(), this->vchMsg.end());
|
||||
}
|
||||
|
||||
bool CAlert::IsInEffect() const
|
||||
{
|
||||
return (GetAdjustedTime() < nExpiration);
|
||||
}
|
||||
|
||||
bool CAlert::Cancels(const CAlert& alert) const
|
||||
{
|
||||
if (!IsInEffect())
|
||||
return false; // this was a no-op before 31403
|
||||
return (alert.nID <= nCancel || setCancel.count(alert.nID));
|
||||
}
|
||||
|
||||
bool CAlert::AppliesTo(int nVersion, std::string strSubVerIn) const
|
||||
{
|
||||
// TODO: rework for client-version-embedded-in-strSubVer ?
|
||||
return (IsInEffect() &&
|
||||
nMinVer <= nVersion && nVersion <= nMaxVer &&
|
||||
(setSubVer.empty() || setSubVer.count(strSubVerIn)));
|
||||
}
|
||||
|
||||
bool CAlert::AppliesToMe() const
|
||||
{
|
||||
return AppliesTo(PROTOCOL_VERSION, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector<std::string>()));
|
||||
}
|
||||
|
||||
bool CAlert::RelayTo(CNode* pnode) const
|
||||
{
|
||||
if (!IsInEffect())
|
||||
return false;
|
||||
// returns true if wasn't already contained in the set
|
||||
if (pnode->setKnown.insert(GetHash()).second)
|
||||
{
|
||||
if (AppliesTo(pnode->nVersion, pnode->strSubVer) ||
|
||||
AppliesToMe() ||
|
||||
GetAdjustedTime() < nRelayUntil)
|
||||
{
|
||||
pnode->PushMessage("alert", *this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CAlert::CheckSignature() const
|
||||
{
|
||||
// Alert key system disabled for decentralization - v5 hard fork
|
||||
const char* pszKey = fTestNet ? pszTestKey : pszMainKey;
|
||||
if (pszKey[0] == '\0')
|
||||
return false; // No alerts accepted without a valid key
|
||||
|
||||
CKey key;
|
||||
if (!key.SetPubKey(ParseHex(pszKey)))
|
||||
return error("CAlert::CheckSignature() : SetPubKey failed");
|
||||
if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig))
|
||||
return error("CAlert::CheckSignature() : verify signature failed");
|
||||
|
||||
// Now unserialize the data
|
||||
CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION);
|
||||
sMsg >> *(CUnsignedAlert*)this;
|
||||
return true;
|
||||
}
|
||||
|
||||
CAlert CAlert::getAlertByHash(const uint256 &hash)
|
||||
{
|
||||
CAlert retval;
|
||||
{
|
||||
LOCK(cs_mapAlerts);
|
||||
map<uint256, CAlert>::iterator mi = mapAlerts.find(hash);
|
||||
if(mi != mapAlerts.end())
|
||||
retval = mi->second;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool CAlert::ProcessAlert(bool fThread)
|
||||
{
|
||||
if (!CheckSignature())
|
||||
return false;
|
||||
if (!IsInEffect())
|
||||
return false;
|
||||
|
||||
// alert.nID=max is reserved for if the alert key is
|
||||
// compromised. It must have a pre-defined message,
|
||||
// must never expire, must apply to all versions,
|
||||
// and must cancel all previous
|
||||
// alerts or it will be ignored (so an attacker can't
|
||||
// send an "everything is OK, don't panic" version that
|
||||
// cannot be overridden):
|
||||
int maxInt = std::numeric_limits<int>::max();
|
||||
if (nID == maxInt)
|
||||
{
|
||||
if (!(
|
||||
nExpiration == maxInt &&
|
||||
nCancel == (maxInt-1) &&
|
||||
nMinVer == 0 &&
|
||||
nMaxVer == maxInt &&
|
||||
setSubVer.empty() &&
|
||||
nPriority == maxInt &&
|
||||
strStatusBar == "URGENT: Alert key compromised, upgrade required"
|
||||
))
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
LOCK(cs_mapAlerts);
|
||||
// Cancel previous alerts
|
||||
for (map<uint256, CAlert>::iterator mi = mapAlerts.begin(); mi != mapAlerts.end();)
|
||||
{
|
||||
const CAlert& alert = (*mi).second;
|
||||
if (Cancels(alert))
|
||||
{
|
||||
printf("cancelling alert %d\n", alert.nID);
|
||||
uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED);
|
||||
mapAlerts.erase(mi++);
|
||||
}
|
||||
else if (!alert.IsInEffect())
|
||||
{
|
||||
printf("expiring alert %d\n", alert.nID);
|
||||
uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED);
|
||||
mapAlerts.erase(mi++);
|
||||
}
|
||||
else
|
||||
mi++;
|
||||
}
|
||||
|
||||
// Check if this alert has been cancelled
|
||||
for (auto& item : mapAlerts)
|
||||
{
|
||||
const CAlert& alert = item.second;
|
||||
if (alert.Cancels(*this))
|
||||
{
|
||||
printf("alert already cancelled by %d\n", alert.nID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to mapAlerts
|
||||
mapAlerts.insert(make_pair(GetHash(), *this));
|
||||
// Notify UI and -alertnotify if it applies to me
|
||||
if(AppliesToMe())
|
||||
{
|
||||
uiInterface.NotifyAlertChanged(GetHash(), CT_NEW);
|
||||
std::string strCmd = GetArg("-alertnotify", "");
|
||||
if (!strCmd.empty())
|
||||
{
|
||||
// Alert text should be plain ascii coming from a trusted source, but to
|
||||
// be safe we first strip anything not in safeChars, then add single quotes around
|
||||
// the whole string before passing it to the shell:
|
||||
std::string singleQuote("'");
|
||||
// safeChars chosen to allow simple messages/URLs/email addresses, but avoid anything
|
||||
// even possibly remotely dangerous like & or >
|
||||
std::string safeChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890 .,;_/:?@");
|
||||
std::string safeStatus;
|
||||
for (std::string::size_type i = 0; i < strStatusBar.size(); i++)
|
||||
{
|
||||
if (safeChars.find(strStatusBar[i]) != std::string::npos)
|
||||
safeStatus.push_back(strStatusBar[i]);
|
||||
}
|
||||
safeStatus = singleQuote+safeStatus+singleQuote;
|
||||
boost::replace_all(strCmd, "%s", safeStatus);
|
||||
|
||||
if (fThread)
|
||||
boost::thread t(runCommand, strCmd); // thread runs free
|
||||
else
|
||||
runCommand(strCmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe());
|
||||
return true;
|
||||
}
|
||||
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2012 The Bitcoin developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef _TRIANGLESALERT_H_
|
||||
#define _TRIANGLESALERT_H_ 1
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "uint256.h"
|
||||
#include "util.h"
|
||||
|
||||
class CNode;
|
||||
|
||||
/** Alerts are for notifying old versions if they become too obsolete and
|
||||
* need to upgrade. The message is displayed in the status bar.
|
||||
* Alert messages are broadcast as a vector of signed data. Unserializing may
|
||||
* not read the entire buffer if the alert is for a newer version, but older
|
||||
* versions can still relay the original data.
|
||||
*/
|
||||
class CUnsignedAlert
|
||||
{
|
||||
public:
|
||||
int nVersion;
|
||||
int64_t nRelayUntil; // when newer nodes stop relaying to newer nodes
|
||||
int64_t nExpiration;
|
||||
int nID;
|
||||
int nCancel;
|
||||
std::set<int> setCancel;
|
||||
int nMinVer; // lowest version inclusive
|
||||
int nMaxVer; // highest version inclusive
|
||||
std::set<std::string> setSubVer; // empty matches all
|
||||
int nPriority;
|
||||
|
||||
// Actions
|
||||
std::string strComment;
|
||||
std::string strStatusBar;
|
||||
std::string strReserved;
|
||||
|
||||
IMPLEMENT_SERIALIZE
|
||||
(
|
||||
READWRITE(this->nVersion);
|
||||
nVersion = this->nVersion;
|
||||
READWRITE(nRelayUntil);
|
||||
READWRITE(nExpiration);
|
||||
READWRITE(nID);
|
||||
READWRITE(nCancel);
|
||||
READWRITE(setCancel);
|
||||
READWRITE(nMinVer);
|
||||
READWRITE(nMaxVer);
|
||||
READWRITE(setSubVer);
|
||||
READWRITE(nPriority);
|
||||
|
||||
READWRITE(strComment);
|
||||
READWRITE(strStatusBar);
|
||||
READWRITE(strReserved);
|
||||
)
|
||||
|
||||
void SetNull();
|
||||
|
||||
std::string ToString() const;
|
||||
void print() const;
|
||||
};
|
||||
|
||||
/** An alert is a combination of a serialized CUnsignedAlert and a signature. */
|
||||
class CAlert : public CUnsignedAlert
|
||||
{
|
||||
public:
|
||||
std::vector<unsigned char> vchMsg;
|
||||
std::vector<unsigned char> vchSig;
|
||||
|
||||
CAlert()
|
||||
{
|
||||
SetNull();
|
||||
}
|
||||
|
||||
IMPLEMENT_SERIALIZE
|
||||
(
|
||||
READWRITE(vchMsg);
|
||||
READWRITE(vchSig);
|
||||
)
|
||||
|
||||
void SetNull();
|
||||
bool IsNull() const;
|
||||
uint256 GetHash() const;
|
||||
bool IsInEffect() const;
|
||||
bool Cancels(const CAlert& alert) const;
|
||||
bool AppliesTo(int nVersion, std::string strSubVerIn) const;
|
||||
bool AppliesToMe() const;
|
||||
bool RelayTo(CNode* pnode) const;
|
||||
bool CheckSignature() const;
|
||||
bool ProcessAlert(bool fThread = true);
|
||||
|
||||
/*
|
||||
* Get copy of (active) alert object by hash. Returns a null alert if it is not found.
|
||||
*/
|
||||
static CAlert getAlertByHash(const uint256 &hash);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
+39
-37
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
|
||||
#ifdef WIN32
|
||||
@@ -55,7 +55,7 @@ public:
|
||||
// For all pages in affected range, increase lock count
|
||||
void LockRange(void *p, size_t size)
|
||||
{
|
||||
boost::mutex::scoped_lock lock(mutex);
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if(!size) return;
|
||||
const size_t base_addr = reinterpret_cast<size_t>(p);
|
||||
const size_t start_page = base_addr & page_mask;
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
if(it == histogram.end()) // Newly locked page
|
||||
{
|
||||
locker.Lock(reinterpret_cast<void*>(page), page_size);
|
||||
histogram.insert(std::make_pair(page, 1));
|
||||
histogram.insert({page, 1});
|
||||
}
|
||||
else // Page was already locked; increase counter
|
||||
{
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
// For all pages in affected range, decrease lock count
|
||||
void UnlockRange(void *p, size_t size)
|
||||
{
|
||||
boost::mutex::scoped_lock lock(mutex);
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if(!size) return;
|
||||
const size_t base_addr = reinterpret_cast<size_t>(p);
|
||||
const size_t start_page = base_addr & page_mask;
|
||||
@@ -101,13 +101,13 @@ public:
|
||||
// Get number of locked pages for diagnostics
|
||||
int GetLockedPageCount()
|
||||
{
|
||||
boost::mutex::scoped_lock lock(mutex);
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return histogram.size();
|
||||
}
|
||||
|
||||
private:
|
||||
Locker locker;
|
||||
boost::mutex mutex;
|
||||
std::mutex mutex;
|
||||
size_t page_size, page_mask;
|
||||
// map of page base address to lock count
|
||||
typedef std::map<size_t,int> Histogram;
|
||||
@@ -182,35 +182,36 @@ private:
|
||||
template<typename T>
|
||||
struct secure_allocator : public std::allocator<T>
|
||||
{
|
||||
// MSVC8 default copy constructor is broken
|
||||
// C++20 removed pointer/reference/etc. member typedefs from std::allocator
|
||||
// and removed the 2-arg allocate(n, hint). Define what we still need
|
||||
// directly instead of pulling from base.
|
||||
typedef std::allocator<T> base;
|
||||
typedef typename base::size_type size_type;
|
||||
typedef typename base::difference_type difference_type;
|
||||
typedef typename base::pointer pointer;
|
||||
typedef typename base::const_pointer const_pointer;
|
||||
typedef typename base::reference reference;
|
||||
typedef typename base::const_reference const_reference;
|
||||
typedef typename base::value_type value_type;
|
||||
secure_allocator() throw() {}
|
||||
secure_allocator(const secure_allocator& a) throw() : base(a) {}
|
||||
typedef T value_type;
|
||||
typedef T* pointer;
|
||||
typedef const T* const_pointer;
|
||||
typedef T& reference;
|
||||
typedef const T& const_reference;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
secure_allocator() noexcept {}
|
||||
secure_allocator(const secure_allocator& a) noexcept : base(a) {}
|
||||
template <typename U>
|
||||
secure_allocator(const secure_allocator<U>& a) throw() : base(a) {}
|
||||
~secure_allocator() throw() {}
|
||||
secure_allocator(const secure_allocator<U>& a) noexcept : base(a) {}
|
||||
~secure_allocator() noexcept {}
|
||||
template<typename _Other> struct rebind
|
||||
{ typedef secure_allocator<_Other> other; };
|
||||
|
||||
T* allocate(std::size_t n, const void *hint = 0)
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
T *p;
|
||||
p = std::allocator<T>::allocate(n, hint);
|
||||
if (p != NULL)
|
||||
T* p = std::allocator<T>::allocate(n);
|
||||
if (p != nullptr)
|
||||
LockedPageManager::instance.LockRange(p, sizeof(T) * n);
|
||||
return p;
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
if (p != NULL)
|
||||
if (p != nullptr)
|
||||
{
|
||||
memset(p, 0, sizeof(T) * n);
|
||||
LockedPageManager::instance.UnlockRange(p, sizeof(T) * n);
|
||||
@@ -226,33 +227,34 @@ struct secure_allocator : public std::allocator<T>
|
||||
template<typename T>
|
||||
struct zero_after_free_allocator : public std::allocator<T>
|
||||
{
|
||||
// MSVC8 default copy constructor is broken
|
||||
// C++20 removed pointer/reference/etc. member typedefs from std::allocator.
|
||||
// Define what we still need directly instead of pulling from base.
|
||||
typedef std::allocator<T> base;
|
||||
typedef typename base::size_type size_type;
|
||||
typedef typename base::difference_type difference_type;
|
||||
typedef typename base::pointer pointer;
|
||||
typedef typename base::const_pointer const_pointer;
|
||||
typedef typename base::reference reference;
|
||||
typedef typename base::const_reference const_reference;
|
||||
typedef typename base::value_type value_type;
|
||||
zero_after_free_allocator() throw() {}
|
||||
zero_after_free_allocator(const zero_after_free_allocator& a) throw() : base(a) {}
|
||||
typedef T value_type;
|
||||
typedef T* pointer;
|
||||
typedef const T* const_pointer;
|
||||
typedef T& reference;
|
||||
typedef const T& const_reference;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
zero_after_free_allocator() noexcept {}
|
||||
zero_after_free_allocator(const zero_after_free_allocator& a) noexcept : base(a) {}
|
||||
template <typename U>
|
||||
zero_after_free_allocator(const zero_after_free_allocator<U>& a) throw() : base(a) {}
|
||||
~zero_after_free_allocator() throw() {}
|
||||
zero_after_free_allocator(const zero_after_free_allocator<U>& a) noexcept : base(a) {}
|
||||
~zero_after_free_allocator() noexcept {}
|
||||
template<typename _Other> struct rebind
|
||||
{ typedef zero_after_free_allocator<_Other> other; };
|
||||
|
||||
void deallocate(T* p, std::size_t n)
|
||||
{
|
||||
if (p != NULL)
|
||||
if (p != nullptr)
|
||||
memset(p, 0, sizeof(T) * n);
|
||||
std::allocator<T>::deallocate(p, n);
|
||||
}
|
||||
};
|
||||
|
||||
// This is exactly like std::string, but with a custom allocator.
|
||||
typedef std::basic_string<char, std::char_traits<char>, secure_allocator<char> > SecureString;
|
||||
using SecureString = std::basic_string<char, std::char_traits<char>, secure_allocator<char>>;
|
||||
|
||||
static inline SecureString MakeSecureString(const std::string& value)
|
||||
{
|
||||
|
||||
+23
-14
@@ -11,7 +11,9 @@
|
||||
#include "version.h"
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/opensslv.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
@@ -36,20 +38,20 @@ public:
|
||||
CAutoBN_CTX()
|
||||
{
|
||||
pctx = BN_CTX_new();
|
||||
if (pctx == NULL)
|
||||
if (pctx == nullptr)
|
||||
throw bignum_error("CAutoBN_CTX : BN_CTX_new() returned NULL");
|
||||
}
|
||||
|
||||
~CAutoBN_CTX()
|
||||
{
|
||||
if (pctx != NULL)
|
||||
if (pctx != nullptr)
|
||||
BN_CTX_free(pctx);
|
||||
}
|
||||
|
||||
operator BN_CTX*() { return pctx; }
|
||||
BN_CTX& operator*() { return *pctx; }
|
||||
BN_CTX** operator&() { return &pctx; }
|
||||
bool operator!() { return (pctx == NULL); }
|
||||
bool operator!() { return (pctx == nullptr); }
|
||||
};
|
||||
|
||||
|
||||
@@ -63,14 +65,14 @@ public:
|
||||
CBigNum()
|
||||
{
|
||||
pbn = BN_new();
|
||||
if (pbn == NULL)
|
||||
if (pbn == nullptr)
|
||||
throw bignum_error("CBigNum::CBigNum() : BN_new() returned NULL");
|
||||
}
|
||||
|
||||
CBigNum(const CBigNum& b)
|
||||
{
|
||||
pbn = BN_new();
|
||||
if (pbn == NULL)
|
||||
if (pbn == nullptr)
|
||||
throw bignum_error("CBigNum::CBigNum(const CBigNum&) : BN_new() returned NULL");
|
||||
if (!BN_copy(pbn, b.pbn))
|
||||
{
|
||||
@@ -88,7 +90,7 @@ public:
|
||||
|
||||
~CBigNum()
|
||||
{
|
||||
if (pbn != NULL)
|
||||
if (pbn != nullptr)
|
||||
BN_clear_free(pbn);
|
||||
}
|
||||
|
||||
@@ -219,7 +221,7 @@ public:
|
||||
|
||||
uint64_t getuint64()
|
||||
{
|
||||
unsigned int nSize = BN_bn2mpi(pbn, NULL);
|
||||
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
|
||||
if (nSize < 4)
|
||||
return 0;
|
||||
std::vector<unsigned char> vch(nSize);
|
||||
@@ -289,7 +291,7 @@ public:
|
||||
|
||||
uint256 getuint256() const
|
||||
{
|
||||
unsigned int nSize = BN_bn2mpi(pbn, NULL);
|
||||
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
|
||||
if (nSize < 4)
|
||||
return 0;
|
||||
std::vector<unsigned char> vch(nSize);
|
||||
@@ -320,7 +322,7 @@ public:
|
||||
|
||||
std::vector<unsigned char> getvch() const
|
||||
{
|
||||
unsigned int nSize = BN_bn2mpi(pbn, NULL);
|
||||
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
|
||||
if (nSize <= 4)
|
||||
return std::vector<unsigned char>();
|
||||
std::vector<unsigned char> vch(nSize);
|
||||
@@ -344,7 +346,7 @@ public:
|
||||
|
||||
unsigned int GetCompact() const
|
||||
{
|
||||
unsigned int nSize = BN_bn2mpi(pbn, NULL);
|
||||
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
|
||||
std::vector<unsigned char> vch(nSize);
|
||||
nSize -= 4;
|
||||
BN_bn2mpi(pbn, &vch[0]);
|
||||
@@ -373,7 +375,7 @@ public:
|
||||
psz++;
|
||||
|
||||
// hex string to bignum
|
||||
static const signed char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 };
|
||||
static constexpr signed char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 };
|
||||
*this = 0;
|
||||
while (isxdigit(*psz))
|
||||
{
|
||||
@@ -514,7 +516,7 @@ public:
|
||||
*/
|
||||
static CBigNum generatePrime(const unsigned int numBits, bool safe = false) {
|
||||
CBigNum ret;
|
||||
if(!BN_generate_prime_ex(ret.pbn, numBits, (safe == true), NULL, NULL, NULL))
|
||||
if(!BN_generate_prime_ex(ret.pbn, numBits, (safe == true), nullptr, nullptr, nullptr))
|
||||
throw bignum_error("CBigNum::generatePrime*= :BN_generate_prime_ex");
|
||||
return ret;
|
||||
}
|
||||
@@ -540,7 +542,14 @@ public:
|
||||
*/
|
||||
bool isPrime(const int checks=BN_prime_checks) const {
|
||||
CAutoBN_CTX pctx;
|
||||
int ret = BN_is_prime_ex(pbn, checks, pctx, NULL);
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
int ret = BN_is_prime_ex(pbn, checks, pctx, nullptr);
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
if(ret < 0){
|
||||
throw bignum_error("CBigNum::isPrime :BN_is_prime_ex");
|
||||
}
|
||||
@@ -705,7 +714,7 @@ inline const CBigNum operator/(const CBigNum& a, const CBigNum& b)
|
||||
{
|
||||
CAutoBN_CTX pctx;
|
||||
CBigNum r;
|
||||
if (!BN_div(r.pbn, NULL, a.pbn, b.pbn, pctx))
|
||||
if (!BN_div(r.pbn, nullptr, a.pbn, b.pbn, pctx))
|
||||
throw bignum_error("CBigNum::operator/ : BN_div failed");
|
||||
return r;
|
||||
}
|
||||
|
||||
+24
-20
@@ -3,10 +3,10 @@
|
||||
|
||||
#include "bootstrap.h"
|
||||
#include "utxosnapshot.h"
|
||||
#include "txdb.h"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
extern bool fTestNet;
|
||||
namespace Checkpoints { bool IsKnownCheckpoint(int nHeight, const uint256& hash); }
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Bootstrap {
|
||||
|
||||
@@ -63,7 +63,7 @@ static SOCKET ConnectDirectTCP(const std::string& host, int port, std::string& s
|
||||
}
|
||||
|
||||
SOCKET hSocket = INVALID_SOCKET;
|
||||
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
||||
for (rp = result; rp != nullptr; rp = rp->ai_next) {
|
||||
hSocket = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (hSocket == INVALID_SOCKET)
|
||||
continue;
|
||||
@@ -208,12 +208,13 @@ bool DownloadFile(const std::string& host, const std::string& urlPath,
|
||||
const fs::path& destPath,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError,
|
||||
bool noProxy)
|
||||
bool noProxy,
|
||||
int portOverride)
|
||||
{
|
||||
try {
|
||||
std::string currentHost = host;
|
||||
std::string currentPath = urlPath;
|
||||
int currentPort = PORT;
|
||||
int currentPort = (portOverride > 0) ? portOverride : PORT;
|
||||
bool useSSL = false;
|
||||
std::string headerData;
|
||||
int redirectCount = 0;
|
||||
@@ -298,7 +299,7 @@ bool DownloadFile(const std::string& host, const std::string& urlPath,
|
||||
location = headerData.substr(valStart, lineEnd - valStart);
|
||||
else
|
||||
location = headerData.substr(valStart);
|
||||
boost::trim(location);
|
||||
location = TrimString(location);
|
||||
|
||||
// Parse redirect URL — supports http://, https://, and relative paths
|
||||
if (location.compare(0, 7, "http://") == 0 ||
|
||||
@@ -414,7 +415,7 @@ bool FetchFileList(const std::string& host,
|
||||
files.clear();
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
boost::trim(line);
|
||||
line = TrimString(line);
|
||||
if (!line.empty() && line[0] != '#')
|
||||
files.push_back(line);
|
||||
}
|
||||
@@ -574,7 +575,7 @@ bool ParseManifest(const fs::path& manifestPath,
|
||||
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
boost::trim(line);
|
||||
line = TrimString(line);
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
@@ -584,8 +585,8 @@ bool ParseManifest(const fs::path& manifestPath,
|
||||
|
||||
std::string key = line.substr(0, eq);
|
||||
std::string val = line.substr(eq + 1);
|
||||
boost::trim(key);
|
||||
boost::trim(val);
|
||||
key = TrimString(key);
|
||||
val = TrimString(val);
|
||||
|
||||
if (key == "format")
|
||||
manifest.format = std::atoi(val.c_str());
|
||||
@@ -675,7 +676,9 @@ bool DownloadBootstrap(const std::string& host,
|
||||
fs::path tmpTarGz = dataDir / "bootstrap.tar.gz.tmp";
|
||||
std::string tarUrl = std::string(BASE_PATH) + "triangles-bootstrap.tar.gz";
|
||||
|
||||
printf("DownloadBootstrap(): attempting tar.gz download from %s%s\n", host.c_str(), tarUrl.c_str());
|
||||
bool tarDownloaded = DownloadFile(host, tarUrl, tmpTarGz, progressFn, strError, noProxy);
|
||||
printf("DownloadBootstrap(): tarDownloaded=%d result=%s\n", tarDownloaded, strError.c_str());
|
||||
|
||||
if (tarDownloaded) {
|
||||
bool extractOk = ExtractTarGz(tmpTarGz, dataDir, strError);
|
||||
@@ -715,16 +718,16 @@ bool DownloadBootstrap(const std::string& host,
|
||||
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
|
||||
// Check if the archive included a trusted pre-built index for the active
|
||||
// backend with a valid snapshot.manifest. If verified, keep it to skip the
|
||||
// multi-hour FastImportBlockFile() rebuild.
|
||||
fs::path txleveldb = dataDir / "txleveldb";
|
||||
fs::path chainDbPath = GetChainDataDir();
|
||||
fs::path database = dataDir / "database";
|
||||
fs::path manifestPath = dataDir / "snapshot.manifest";
|
||||
|
||||
bool keepIndex = false;
|
||||
|
||||
if (fs::exists(manifestPath) && fs::exists(txleveldb)) {
|
||||
if (fs::exists(manifestPath) && fs::exists(chainDbPath)) {
|
||||
SnapshotManifest manifest;
|
||||
std::string manifestError;
|
||||
|
||||
@@ -751,9 +754,10 @@ bool DownloadBootstrap(const std::string& host,
|
||||
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);
|
||||
printf("Bootstrap: removing extracted %s/ (will rebuild index from blk0001.dat)\n",
|
||||
GetChainDataDir().filename().string().c_str());
|
||||
if (fs::exists(chainDbPath))
|
||||
fs::remove_all(chainDbPath);
|
||||
}
|
||||
|
||||
// Always remove BDB database/ dir (wallet environment from another machine)
|
||||
@@ -788,7 +792,7 @@ bool DownloadUtxoSnapshot(const std::string& host,
|
||||
|
||||
printf("Bootstrap: UTXO snapshot downloaded, loading into database...\n");
|
||||
|
||||
// Load the snapshot into a fresh txleveldb
|
||||
// Load the snapshot into a fresh active chain DB
|
||||
if (!UtxoSnapshot::LoadSnapshot(tmpPath, dataDir, strError)) {
|
||||
fs::remove(tmpPath);
|
||||
return false;
|
||||
|
||||
+9
-7
@@ -7,7 +7,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
namespace Bootstrap {
|
||||
|
||||
@@ -20,16 +20,18 @@ namespace Bootstrap {
|
||||
typedef std::function<void(int64_t, int64_t)> ProgressCallback;
|
||||
|
||||
// Check if data dir already has blockchain data
|
||||
bool NeedsBootstrap(const boost::filesystem::path& dataDir);
|
||||
bool NeedsBootstrap(const std::filesystem::path& dataDir);
|
||||
|
||||
// Download a single file via HTTP GET, write to destPath.
|
||||
// If noProxy is true, bypass Tor SOCKS proxy and connect directly
|
||||
// (used for clearnet bootstrap downloads).
|
||||
// If portOverride is set (>0), uses that port instead of the default PORT.
|
||||
bool DownloadFile(const std::string& host, const std::string& urlPath,
|
||||
const boost::filesystem::path& destPath,
|
||||
const std::filesystem::path& destPath,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError,
|
||||
bool noProxy = false);
|
||||
bool noProxy = false,
|
||||
int portOverride = -1);
|
||||
|
||||
// Fetch the file manifest (list of relative paths to download)
|
||||
bool FetchFileList(const std::string& host,
|
||||
@@ -40,7 +42,7 @@ namespace Bootstrap {
|
||||
// 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,
|
||||
const std::filesystem::path& dataDir,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError);
|
||||
|
||||
@@ -54,7 +56,7 @@ namespace Bootstrap {
|
||||
};
|
||||
|
||||
// Parse a snapshot.manifest file into a SnapshotManifest struct.
|
||||
bool ParseManifest(const boost::filesystem::path& manifestPath,
|
||||
bool ParseManifest(const std::filesystem::path& manifestPath,
|
||||
SnapshotManifest& manifest,
|
||||
std::string& strError);
|
||||
|
||||
@@ -66,7 +68,7 @@ namespace Bootstrap {
|
||||
// This is much faster than downloading the full bootstrap archive.
|
||||
// Returns true if snapshot was downloaded and loaded successfully.
|
||||
bool DownloadUtxoSnapshot(const std::string& host,
|
||||
const boost::filesystem::path& dataDir,
|
||||
const std::filesystem::path& dataDir,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError);
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright (c) 2026 The Triangles developers.
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "chaindb_migrate.h"
|
||||
|
||||
#include "txdb-leveldb.h"
|
||||
#include "txdb-rocksdb.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
struct ChainDbStats
|
||||
{
|
||||
int64_t nRecords = 0;
|
||||
int64_t nUtxos = 0;
|
||||
int64_t nUtxoValue = 0;
|
||||
uint256 hashBestChain = 0;
|
||||
int nDbFormat = 0;
|
||||
};
|
||||
|
||||
bool CollectStats(CTxDBBase& db, ChainDbStats& stats, std::string& strError)
|
||||
{
|
||||
stats = ChainDbStats();
|
||||
|
||||
auto it = db.NewIterator();
|
||||
for (it->Seek(std::string()); it->Valid(); it->Next())
|
||||
stats.nRecords++;
|
||||
|
||||
int nUtxos = 0;
|
||||
stats.nUtxoValue = db.SumUtxoValues(nUtxos);
|
||||
stats.nUtxos = nUtxos;
|
||||
db.ReadHashBestChain(stats.hashBestChain);
|
||||
db.ReadDbFormat(stats.nDbFormat);
|
||||
|
||||
if (stats.nRecords <= 0) {
|
||||
strError = "source chain database contains no records";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StatsMatch(const ChainDbStats& src, const ChainDbStats& dst, std::string& strError)
|
||||
{
|
||||
if (src.nRecords != dst.nRecords) {
|
||||
strError = strprintf("record count mismatch after migration: source=%lld rocksdb=%lld",
|
||||
(long long)src.nRecords, (long long)dst.nRecords);
|
||||
return false;
|
||||
}
|
||||
if (src.nUtxos != dst.nUtxos || src.nUtxoValue != dst.nUtxoValue) {
|
||||
strError = strprintf("UTXO mismatch after migration: source=(%lld,%lld) rocksdb=(%lld,%lld)",
|
||||
(long long)src.nUtxos, (long long)src.nUtxoValue,
|
||||
(long long)dst.nUtxos, (long long)dst.nUtxoValue);
|
||||
return false;
|
||||
}
|
||||
if (src.hashBestChain != dst.hashBestChain) {
|
||||
strError = strprintf("best-chain hash mismatch after migration: source=%s rocksdb=%s",
|
||||
src.hashBestChain.ToString().c_str(),
|
||||
dst.hashBestChain.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
if (src.nDbFormat != dst.nDbFormat) {
|
||||
strError = strprintf("dbformat mismatch after migration: source=%d rocksdb=%d",
|
||||
src.nDbFormat, dst.nDbFormat);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool MaybeMigrateLevelDbToRocksDb(bool fForce, std::string& strError)
|
||||
{
|
||||
strError.clear();
|
||||
|
||||
const fs::path dataDir = GetDataDir();
|
||||
const fs::path levelPath = dataDir / "txleveldb";
|
||||
const fs::path rocksPath = dataDir / "rocksdb";
|
||||
const fs::path markerPath = rocksPath / "MIGRATION_INCOMPLETE";
|
||||
|
||||
if (!fs::exists(levelPath))
|
||||
return true;
|
||||
|
||||
if (fs::exists(rocksPath)) {
|
||||
if (fs::exists(markerPath)) {
|
||||
printf("ChainDB migration: removing incomplete previous RocksDB migration\n");
|
||||
fs::remove_all(rocksPath);
|
||||
}
|
||||
else if (!fForce)
|
||||
return true;
|
||||
else {
|
||||
printf("ChainDB migration: removing existing RocksDB directory due to -migratechaindbforce\n");
|
||||
fs::remove_all(rocksPath);
|
||||
}
|
||||
}
|
||||
|
||||
printf("ChainDB migration: copying LevelDB chain state to RocksDB...\n");
|
||||
printf("ChainDB migration: source=%s destination=%s\n",
|
||||
levelPath.string().c_str(), rocksPath.string().c_str());
|
||||
|
||||
try {
|
||||
fs::create_directories(rocksPath);
|
||||
{
|
||||
std::ofstream marker(markerPath);
|
||||
marker << "RocksDB migration in progress. Safe to delete this directory and retry.\n";
|
||||
}
|
||||
|
||||
CTxDB source("r");
|
||||
CRocksTxDB destination("c+");
|
||||
|
||||
ChainDbStats srcStats;
|
||||
if (!CollectStats(source, srcStats, strError)) {
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!destination.TxnBegin()) {
|
||||
strError = "failed to begin RocksDB migration batch";
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t nCopied = 0;
|
||||
auto it = source.NewIterator();
|
||||
for (it->Seek(std::string()); it->Valid(); it->Next())
|
||||
{
|
||||
if (!destination.WriteRawRecordForMigration(it->KeyStr(), it->ValueStr())) {
|
||||
destination.TxnAbort();
|
||||
strError = "failed to write migrated record to RocksDB";
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (++nCopied % 100000 == 0)
|
||||
{
|
||||
if (!destination.TxnCommit()) {
|
||||
strError = "failed to commit RocksDB migration batch";
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
printf("ChainDB migration: copied %lld / %lld records\n",
|
||||
(long long)nCopied, (long long)srcStats.nRecords);
|
||||
if (!destination.TxnBegin()) {
|
||||
strError = "failed to begin RocksDB migration batch";
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!destination.TxnCommit()) {
|
||||
strError = "failed to commit final RocksDB migration batch";
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
ChainDbStats dstStats;
|
||||
if (!CollectStats(destination, dstStats, strError)) {
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
if (!StatsMatch(srcStats, dstStats, strError)) {
|
||||
source.Close();
|
||||
destination.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("ChainDB migration: verified %lld records, %lld UTXOs, best=%s\n",
|
||||
(long long)dstStats.nRecords,
|
||||
(long long)dstStats.nUtxos,
|
||||
dstStats.hashBestChain.ToString().substr(0,20).c_str());
|
||||
|
||||
source.Close();
|
||||
destination.Close();
|
||||
fs::remove(markerPath);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
strError = e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("ChainDB migration: complete. Legacy LevelDB was left untouched at %s\n",
|
||||
levelPath.string().c_str());
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2026 The Triangles developers.
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#ifndef TRIANGLES_CHAINDB_MIGRATE_H
|
||||
#define TRIANGLES_CHAINDB_MIGRATE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
// Migrate legacy LevelDB chain state from <datadir>/txleveldb to RocksDB in
|
||||
// <datadir>/rocksdb. The source is never modified. Returns true when migration
|
||||
// succeeds or when there is nothing to migrate.
|
||||
bool MaybeMigrateLevelDbToRocksDb(bool fForce, std::string& strError);
|
||||
|
||||
#endif // TRIANGLES_CHAINDB_MIGRATE_H
|
||||
+42
-28
@@ -25,21 +25,27 @@ namespace Checkpoints
|
||||
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
|
||||
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
|
||||
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
|
||||
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
|
||||
{ 3935, uint256("0xe16290c9757d1368b8d7c35de4f8f70c2c9f9c785b667df0c3bff85086ca6")},
|
||||
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
|
||||
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
|
||||
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
|
||||
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
|
||||
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
|
||||
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
|
||||
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
|
||||
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
|
||||
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
|
||||
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
|
||||
{2206000, uint256("0x780ae878f8b10b6cbd51ceb2c0799c90d3551fa916be62974375071b9e36581c")},
|
||||
{2207000, uint256("0x8836d67b0f08036c4a7c26ff0a29d4461a52b6d8f552165ad9c1abec2f3cadfd")},
|
||||
{2208000, uint256("0xe4a19e8a29fa7aae47f7563377af3e896fee18ed069a64e325b8c2c6c820a1be")},
|
||||
{2209000, uint256("0x04c78a6fc863bed918a9364c58c64489943b2e85d84ddb1ac2fba584f390d5dc")},
|
||||
};
|
||||
|
||||
// Published UTXO snapshot file SHA256, keyed by snapshot height.
|
||||
// Each entry binds height -> SHA256 of the canonical snapshot file produced by
|
||||
// UtxoSnapshot::DumpSnapshot at that height. Used by SnapshotNet to verify
|
||||
// P2P-delivered snapshots without trusting any peer.
|
||||
//
|
||||
// Maintainers: after producing a snapshot, sha256 the file and add an entry
|
||||
// here. The corresponding (height, blockhash) must already exist in
|
||||
// mapCheckpoints / mapCheckpointsTestnet.
|
||||
static std::map<int, uint256> mapSnapshotHashes = {
|
||||
};
|
||||
|
||||
static std::map<int, uint256> mapSnapshotHashesTestnet = {
|
||||
};
|
||||
|
||||
static MapCheckpoints mapCheckpointsTestnet = {
|
||||
@@ -48,21 +54,13 @@ namespace Checkpoints
|
||||
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
|
||||
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
|
||||
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
|
||||
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
|
||||
{ 3935, uint256("0xe16290c9757d1368b8d7c35de4f8f70c2c9f9c785b667df0c3bff85086ca6")},
|
||||
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
|
||||
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
|
||||
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
|
||||
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
|
||||
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
|
||||
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
|
||||
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
|
||||
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
|
||||
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
|
||||
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
|
||||
{2206000, uint256("0x780ae878f8b10b6cbd51ceb2c0799c90d3551fa916be62974375071b9e36581c")},
|
||||
{2207000, uint256("0x8836d67b0f08036c4a7c26ff0a29d4461a52b6d8f552165ad9c1abec2f3cadfd")},
|
||||
{2208000, uint256("0xe4a19e8a29fa7aae47f7563377af3e896fee18ed069a64e325b8c2c6c820a1be")},
|
||||
{2209000, uint256("0x04c78a6fc863bed918a9364c58c64489943b2e85d84ddb1ac2fba584f390d5dc")},
|
||||
};
|
||||
|
||||
bool CheckHardened(int nHeight, const uint256& hash)
|
||||
@@ -89,6 +87,22 @@ namespace Checkpoints
|
||||
return checkpoints.rbegin()->first;
|
||||
}
|
||||
|
||||
int GetBestSnapshotHeight()
|
||||
{
|
||||
std::map<int, uint256>& snaps = (fTestNet ? mapSnapshotHashesTestnet : mapSnapshotHashes);
|
||||
if (snaps.empty()) return 0;
|
||||
return snaps.rbegin()->first;
|
||||
}
|
||||
|
||||
bool GetSnapshotHash(int nHeight, uint256& fileHashOut)
|
||||
{
|
||||
std::map<int, uint256>& snaps = (fTestNet ? mapSnapshotHashesTestnet : mapSnapshotHashes);
|
||||
auto it = snaps.find(nHeight);
|
||||
if (it == snaps.end()) return false;
|
||||
fileHashOut = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
CBlockIndex* GetLastCheckpoint(const std::map<uint256, CBlockIndex*>& mapBlockIndex)
|
||||
{
|
||||
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
|
||||
@@ -100,7 +114,7 @@ namespace Checkpoints
|
||||
if (t != mapBlockIndex.end())
|
||||
return t->second;
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// triangles: synchronized checkpoint (centrally broadcasted)
|
||||
@@ -119,7 +133,7 @@ namespace Checkpoints
|
||||
error("GetSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str());
|
||||
else
|
||||
return mapBlockIndex[hashSyncCheckpoint];
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// triangles: only descendant of current sync-checkpoint is allowed
|
||||
@@ -167,7 +181,7 @@ namespace Checkpoints
|
||||
|
||||
bool WriteSyncCheckpoint(const uint256& hashCheckpoint)
|
||||
{
|
||||
CTxDB txdb;
|
||||
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
|
||||
txdb.TxnBegin();
|
||||
if (!txdb.WriteSyncCheckpoint(hashCheckpoint))
|
||||
{
|
||||
@@ -193,7 +207,7 @@ namespace Checkpoints
|
||||
return false;
|
||||
}
|
||||
|
||||
CTxDB txdb;
|
||||
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
|
||||
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashPendingCheckpoint];
|
||||
if (!pindexCheckpoint->IsInMainChain())
|
||||
{
|
||||
@@ -249,8 +263,8 @@ namespace Checkpoints
|
||||
return false;
|
||||
if (hashBlock == hashPendingCheckpoint)
|
||||
return true;
|
||||
if (mapOrphanBlocks.count(hashPendingCheckpoint)
|
||||
&& hashBlock == WantedByOrphan(mapOrphanBlocks[hashPendingCheckpoint]))
|
||||
if (mapOrphanBlocks.count(hashPendingCheckpoint)
|
||||
&& hashBlock == WantedByOrphan(mapOrphanBlocks[hashPendingCheckpoint].get()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -264,7 +278,7 @@ namespace Checkpoints
|
||||
{
|
||||
// checkpoint block accepted but not yet in main chain
|
||||
printf("ResetSyncCheckpoint: SetBestChain to hardened checkpoint %s\n", hash.ToString().c_str());
|
||||
CTxDB txdb;
|
||||
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
|
||||
CBlock block;
|
||||
if (!block.ReadFromDisk(mapBlockIndex[hash]))
|
||||
return error("ResetSyncCheckpoint: ReadFromDisk failed for hardened checkpoint %s", hash.ToString().c_str());
|
||||
@@ -339,7 +353,7 @@ namespace Checkpoints
|
||||
if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig))
|
||||
return error("SendSyncCheckpoint: Unable to sign checkpoint, check private key?");
|
||||
|
||||
if(!checkpoint.ProcessSyncCheckpoint(NULL))
|
||||
if(!checkpoint.ProcessSyncCheckpoint(nullptr))
|
||||
{
|
||||
printf("WARNING: SendSyncCheckpoint: Failed to process checkpoint.\n");
|
||||
return false;
|
||||
@@ -400,7 +414,7 @@ bool CSyncCheckpoint::ProcessSyncCheckpoint(CNode* pfrom)
|
||||
pfrom->PushGetBlocks(pindexBest, hashCheckpoint);
|
||||
// ask directly as well in case rejected earlier by duplicate
|
||||
// proof-of-stake because getblocks may not get it this time
|
||||
pfrom->AskFor(CInv(MSG_BLOCK, mapOrphanBlocks.count(hashCheckpoint)? WantedByOrphan(mapOrphanBlocks[hashCheckpoint]) : hashCheckpoint));
|
||||
pfrom->AskFor(CInv(MSG_BLOCK, mapOrphanBlocks.count(hashCheckpoint)? WantedByOrphan(mapOrphanBlocks[hashCheckpoint].get()) : hashCheckpoint));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -408,7 +422,7 @@ bool CSyncCheckpoint::ProcessSyncCheckpoint(CNode* pfrom)
|
||||
if (!Checkpoints::ValidateSyncCheckpoint(hashCheckpoint))
|
||||
return false;
|
||||
|
||||
CTxDB txdb;
|
||||
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
|
||||
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashCheckpoint];
|
||||
if (!pindexCheckpoint->IsInMainChain())
|
||||
{
|
||||
|
||||
@@ -45,6 +45,11 @@ namespace Checkpoints
|
||||
// Return conservative estimate of total number of blocks, 0 if unknown
|
||||
int GetTotalBlocksEstimate();
|
||||
|
||||
// Return the highest checkpoint height that has a published UTXO snapshot
|
||||
// hash, along with the snapshot's file SHA256. Returns 0 height if none.
|
||||
int GetBestSnapshotHeight();
|
||||
bool GetSnapshotHash(int nHeight, uint256& fileHashOut);
|
||||
|
||||
// Returns last CBlockIndex* in mapBlockIndex that is a checkpoint
|
||||
CBlockIndex* GetLastCheckpoint(const std::map<uint256, CBlockIndex*>& mapBlockIndex);
|
||||
|
||||
|
||||
+12
-12
@@ -9,17 +9,17 @@
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/thread/condition_variable.hpp>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/thread.hpp>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
template<typename T>
|
||||
class CCheckQueue
|
||||
{
|
||||
private:
|
||||
boost::mutex mutex;
|
||||
boost::condition_variable condWorker;
|
||||
boost::condition_variable condMaster;
|
||||
std::mutex mutex;
|
||||
std::condition_variable condWorker;
|
||||
std::condition_variable condMaster;
|
||||
|
||||
std::deque<T> queue;
|
||||
unsigned int nIdle;
|
||||
@@ -32,7 +32,7 @@ private:
|
||||
|
||||
bool Loop(bool fMaster)
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(mutex);
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
if (!fMaster)
|
||||
nTotal++;
|
||||
nIdle++;
|
||||
@@ -72,10 +72,10 @@ private:
|
||||
nIdle--;
|
||||
lock.unlock();
|
||||
|
||||
for (unsigned int i = 0; i < vChecks.size(); i++)
|
||||
for (auto& check : vChecks)
|
||||
{
|
||||
if (fOk)
|
||||
fOk = vChecks[i]();
|
||||
fOk = check();
|
||||
}
|
||||
vChecks.clear();
|
||||
|
||||
@@ -102,7 +102,7 @@ public:
|
||||
|
||||
void StartBatch()
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(mutex);
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
fAllOk = true;
|
||||
nTodo = 0;
|
||||
}
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
if (vChecks.empty())
|
||||
return;
|
||||
|
||||
boost::unique_lock<boost::mutex> lock(mutex);
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
for (typename std::vector<T>::iterator it = vChecks.begin(); it != vChecks.end(); ++it)
|
||||
{
|
||||
queue.push_back(T());
|
||||
@@ -132,7 +132,7 @@ public:
|
||||
|
||||
void Quit()
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(mutex);
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
fQuit = true;
|
||||
condWorker.notify_all();
|
||||
condMaster.notify_all();
|
||||
|
||||
+1
-1
@@ -8,7 +8,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 9
|
||||
#define CLIENT_VERSION_REVISION 2
|
||||
#define CLIENT_VERSION_REVISION 9
|
||||
#define CLIENT_VERSION_BUILD 0
|
||||
|
||||
// Converts the parameter X to a string after macro replacement on X has been performed.
|
||||
|
||||
+2
-2
@@ -75,7 +75,7 @@ bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned
|
||||
|
||||
bool fOk = true;
|
||||
|
||||
if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, chKey, chIV);
|
||||
if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, chKey, chIV);
|
||||
if (fOk) fOk = EVP_EncryptUpdate(ctx, &vchCiphertext[0], &nCLen, &vchPlaintext[0], nLen);
|
||||
if (fOk) fOk = EVP_EncryptFinal_ex(ctx, (&vchCiphertext[0])+nCLen, &nFLen);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
@@ -102,7 +102,7 @@ bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingM
|
||||
|
||||
bool fOk = true;
|
||||
|
||||
if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, chKey, chIV);
|
||||
if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, chKey, chIV);
|
||||
if (fOk) fOk = EVP_DecryptUpdate(ctx, &vchPlaintext[0], &nPLen, &vchCiphertext[0], nLen);
|
||||
if (fOk) fOk = EVP_DecryptFinal_ex(ctx, (&vchPlaintext[0])+nPLen, &nFLen);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
+3
-1
@@ -8,6 +8,8 @@
|
||||
#include "key.h"
|
||||
#include "serialize.h"
|
||||
|
||||
#include <openssl/crypto.h> /* for OPENSSL_cleanse */
|
||||
|
||||
const unsigned int WALLET_CRYPTO_KEY_SIZE = 32;
|
||||
const unsigned int WALLET_CRYPTO_SALT_SIZE = 8;
|
||||
|
||||
@@ -78,7 +80,7 @@ public:
|
||||
|
||||
};
|
||||
|
||||
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;
|
||||
using CKeyingMaterial = std::vector<unsigned char, secure_allocator<unsigned char>>;
|
||||
|
||||
/** Encryption/decryption context with key information */
|
||||
class CCrypter
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2026 The Triangles developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "crypto_ecdh.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_ecdh.h>
|
||||
|
||||
namespace {
|
||||
|
||||
// One process-wide context is sufficient for ECDH — no signing or verification
|
||||
// flags needed. Created lazily on first use; libsecp256k1 contexts are
|
||||
// thread-safe for read-only operations like ECDH.
|
||||
secp256k1_context* GetECDHContext()
|
||||
{
|
||||
static std::once_flag once;
|
||||
static secp256k1_context* ctx = nullptr;
|
||||
std::call_once(once, []() {
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
});
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// Hash function callback that returns the raw X coordinate of the shared
|
||||
// point. Mirrors OpenSSL's ECDH_compute_key behaviour when the KDF is nullptr.
|
||||
int hash_xonly(unsigned char* output,
|
||||
const unsigned char* x32,
|
||||
const unsigned char* /*y32*/,
|
||||
void* /*data*/)
|
||||
{
|
||||
std::memcpy(output, x32, 32);
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ECDH_xonly_secp256k1(unsigned char out32[32],
|
||||
const unsigned char privkey32[32],
|
||||
const unsigned char* pubkey,
|
||||
std::size_t pubkey_len)
|
||||
{
|
||||
if (pubkey_len != 33 && pubkey_len != 65) return false;
|
||||
|
||||
secp256k1_context* ctx = GetECDHContext();
|
||||
if (!ctx) return false;
|
||||
|
||||
secp256k1_pubkey pk;
|
||||
if (!secp256k1_ec_pubkey_parse(ctx, &pk, pubkey, pubkey_len))
|
||||
return false;
|
||||
|
||||
return secp256k1_ecdh(ctx, out32, &pk, privkey32, hash_xonly, nullptr) == 1;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2026 The Triangles developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#ifndef TRIANGLES_CRYPTO_ECDH_H
|
||||
#define TRIANGLES_CRYPTO_ECDH_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
/**
|
||||
* Compute the shared secret X coordinate via secp256k1 ECDH.
|
||||
*
|
||||
* Output matches OpenSSL's ECDH_compute_key(buf, 32, peer_pub, our_priv, NULL)
|
||||
* — i.e. the raw X coordinate of the shared point, with no KDF applied. This
|
||||
* preserves bit-for-bit compatibility with smessage's existing key derivation
|
||||
* (which feeds the X coordinate into SHA-512 itself), so historical encrypted
|
||||
* messages remain decryptable after the migration off OpenSSL EC.
|
||||
*
|
||||
* @param out32 32-byte buffer for the shared X coordinate.
|
||||
* @param privkey32 32-byte secret scalar (big-endian).
|
||||
* @param pubkey Peer public key, serialized as either 33 bytes (compressed)
|
||||
* or 65 bytes (uncompressed).
|
||||
* @param pubkey_len 33 or 65; any other length fails immediately.
|
||||
* @return true on success, false if the public key is malformed or the
|
||||
* private key is invalid (zero / >= curve order).
|
||||
*/
|
||||
bool ECDH_xonly_secp256k1(unsigned char out32[32],
|
||||
const unsigned char privkey32[32],
|
||||
const unsigned char* pubkey,
|
||||
std::size_t pubkey_len);
|
||||
|
||||
#endif // TRIANGLES_CRYPTO_ECDH_H
|
||||
@@ -0,0 +1,389 @@
|
||||
// Copyright (c) 2026 The Triangles developers
|
||||
// Copyright (c) 2015 Pieter Wuille (lax DER parser, MIT licence)
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "crypto_ecdsa.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_recovery.h>
|
||||
|
||||
namespace {
|
||||
|
||||
// Combined VERIFY + SIGN context. libsecp256k1 contexts are thread-safe for
|
||||
// signing and verification once created. In libsecp256k1 >= 0.2 these flags
|
||||
// are accepted but increasingly no-ops; passing both keeps us compatible with
|
||||
// older versions still in distro packages.
|
||||
secp256k1_context* GetEcdsaContext()
|
||||
{
|
||||
static std::once_flag once;
|
||||
static secp256k1_context* ctx = nullptr;
|
||||
std::call_once(once, []() {
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
|
||||
});
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Lax DER parser, vendored from Bitcoin Core (contrib/lax_der_parsing.c).
|
||||
//
|
||||
// libsecp256k1's strict parser rejects DER encodings that OpenSSL has
|
||||
// historically accepted: non-minimal length bytes, extra leading zeros on R/S,
|
||||
// negative integers, etc. Many such signatures already exist on chain. This
|
||||
// parser tolerates them, normalises (R, S) into a 64-byte compact buffer, and
|
||||
// hands that to libsecp256k1's compact-signature parser. Anything that still
|
||||
// fails to fit (e.g. R or S exceeding 32 bytes after stripping leading zeros)
|
||||
// is treated as zero so the verify call returns a clean failure rather than
|
||||
// crashing.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx,
|
||||
secp256k1_ecdsa_signature* sig,
|
||||
const unsigned char* input,
|
||||
std::size_t inputlen)
|
||||
{
|
||||
std::size_t rpos, rlen, spos, slen;
|
||||
std::size_t pos = 0;
|
||||
std::size_t lenbyte;
|
||||
unsigned char tmpsig[64] = {0};
|
||||
int overflow = 0;
|
||||
|
||||
// Initialise sig with a parseable but invalid signature so the caller
|
||||
// always gets a defined value back even on early-exit paths.
|
||||
secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig);
|
||||
|
||||
// SEQUENCE tag.
|
||||
if (pos == inputlen || input[pos] != 0x30) return 0;
|
||||
pos++;
|
||||
|
||||
// SEQUENCE length (skipped — we trust the inner element lengths).
|
||||
if (pos == inputlen) return 0;
|
||||
lenbyte = input[pos++];
|
||||
if (lenbyte & 0x80) {
|
||||
lenbyte -= 0x80;
|
||||
if (lenbyte > inputlen - pos) return 0;
|
||||
pos += lenbyte;
|
||||
}
|
||||
|
||||
// R: INTEGER tag.
|
||||
if (pos == inputlen || input[pos] != 0x02) return 0;
|
||||
pos++;
|
||||
|
||||
// R: length.
|
||||
if (pos == inputlen) return 0;
|
||||
lenbyte = input[pos++];
|
||||
if (lenbyte & 0x80) {
|
||||
lenbyte -= 0x80;
|
||||
if (lenbyte > inputlen - pos) return 0;
|
||||
while (lenbyte > 0 && input[pos] == 0) { pos++; lenbyte--; }
|
||||
if (lenbyte >= sizeof(std::size_t)) return 0;
|
||||
rlen = 0;
|
||||
while (lenbyte > 0) { rlen = (rlen << 8) + input[pos]; pos++; lenbyte--; }
|
||||
} else {
|
||||
rlen = lenbyte;
|
||||
}
|
||||
if (rlen > inputlen - pos) return 0;
|
||||
rpos = pos;
|
||||
pos += rlen;
|
||||
|
||||
// S: INTEGER tag.
|
||||
if (pos == inputlen || input[pos] != 0x02) return 0;
|
||||
pos++;
|
||||
|
||||
// S: length.
|
||||
if (pos == inputlen) return 0;
|
||||
lenbyte = input[pos++];
|
||||
if (lenbyte & 0x80) {
|
||||
lenbyte -= 0x80;
|
||||
if (lenbyte > inputlen - pos) return 0;
|
||||
while (lenbyte > 0 && input[pos] == 0) { pos++; lenbyte--; }
|
||||
if (lenbyte >= sizeof(std::size_t)) return 0;
|
||||
slen = 0;
|
||||
while (lenbyte > 0) { slen = (slen << 8) + input[pos]; pos++; lenbyte--; }
|
||||
} else {
|
||||
slen = lenbyte;
|
||||
}
|
||||
if (slen > inputlen - pos) return 0;
|
||||
spos = pos;
|
||||
|
||||
// Strip leading zeros from R and place right-aligned in tmpsig[0..32).
|
||||
while (rlen > 0 && input[rpos] == 0) { rlen--; rpos++; }
|
||||
if (rlen > 32) {
|
||||
overflow = 1;
|
||||
} else {
|
||||
std::memcpy(tmpsig + 32 - rlen, input + rpos, rlen);
|
||||
}
|
||||
|
||||
// Strip leading zeros from S and place right-aligned in tmpsig[32..64).
|
||||
while (slen > 0 && input[spos] == 0) { slen--; spos++; }
|
||||
if (slen > 32) {
|
||||
overflow = 1;
|
||||
} else {
|
||||
std::memcpy(tmpsig + 64 - slen, input + spos, slen);
|
||||
}
|
||||
|
||||
if (!overflow) {
|
||||
overflow = !secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig);
|
||||
}
|
||||
if (overflow) {
|
||||
std::memset(tmpsig, 0, 64);
|
||||
secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ECDSA_verify_secp256k1(const unsigned char hash32[32],
|
||||
const unsigned char* sig, std::size_t sig_len,
|
||||
const unsigned char* pubkey, std::size_t pubkey_len)
|
||||
{
|
||||
if (sig_len == 0) return false;
|
||||
if (pubkey_len != 33 && pubkey_len != 65) return false;
|
||||
|
||||
secp256k1_context* ctx = GetEcdsaContext();
|
||||
if (!ctx) return false;
|
||||
|
||||
secp256k1_pubkey pk;
|
||||
if (!secp256k1_ec_pubkey_parse(ctx, &pk, pubkey, pubkey_len))
|
||||
return false;
|
||||
|
||||
secp256k1_ecdsa_signature parsed_sig;
|
||||
if (!ecdsa_signature_parse_der_lax(ctx, &parsed_sig, sig, sig_len))
|
||||
return false;
|
||||
|
||||
return secp256k1_ecdsa_verify(ctx, &parsed_sig, hash32, &pk) == 1;
|
||||
}
|
||||
|
||||
bool ECDSA_sign_secp256k1(unsigned char* out, std::size_t* out_len,
|
||||
const unsigned char hash32[32],
|
||||
const unsigned char privkey32[32])
|
||||
{
|
||||
if (!out || !out_len) return false;
|
||||
secp256k1_context* ctx = GetEcdsaContext();
|
||||
if (!ctx) return false;
|
||||
|
||||
secp256k1_ecdsa_signature sig;
|
||||
if (!secp256k1_ecdsa_sign(ctx, &sig, hash32, privkey32, nullptr, nullptr))
|
||||
return false;
|
||||
|
||||
return secp256k1_ecdsa_signature_serialize_der(ctx, out, out_len, &sig) == 1;
|
||||
}
|
||||
|
||||
bool ECDSA_sign_compact_secp256k1(unsigned char out65[65],
|
||||
const unsigned char hash32[32],
|
||||
const unsigned char privkey32[32],
|
||||
bool fCompressed)
|
||||
{
|
||||
secp256k1_context* ctx = GetEcdsaContext();
|
||||
if (!ctx) return false;
|
||||
|
||||
secp256k1_ecdsa_recoverable_signature recsig;
|
||||
if (!secp256k1_ecdsa_sign_recoverable(ctx, &recsig, hash32, privkey32, nullptr, nullptr))
|
||||
return false;
|
||||
|
||||
int recid = -1;
|
||||
if (!secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, &out65[1], &recid, &recsig))
|
||||
return false;
|
||||
if (recid < 0 || recid > 3) return false;
|
||||
|
||||
out65[0] = static_cast<unsigned char>(27 + recid + (fCompressed ? 4 : 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ECDSA_recover_compact_secp256k1(unsigned char* pubkey_out,
|
||||
std::size_t* pubkey_len_out,
|
||||
const unsigned char hash32[32],
|
||||
const unsigned char sig65[65])
|
||||
{
|
||||
if (!pubkey_out || !pubkey_len_out) return false;
|
||||
|
||||
int header = sig65[0];
|
||||
if (header < 27 || header >= 35) return false;
|
||||
bool fCompressed = (header >= 31);
|
||||
int recid = (header - 27) & 0x3;
|
||||
|
||||
secp256k1_context* ctx = GetEcdsaContext();
|
||||
if (!ctx) return false;
|
||||
|
||||
secp256k1_ecdsa_recoverable_signature recsig;
|
||||
if (!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &recsig, &sig65[1], recid))
|
||||
return false;
|
||||
|
||||
secp256k1_pubkey pk;
|
||||
if (!secp256k1_ecdsa_recover(ctx, &pk, &recsig, hash32))
|
||||
return false;
|
||||
|
||||
std::size_t out_len = fCompressed ? 33 : 65;
|
||||
if (!secp256k1_ec_pubkey_serialize(ctx, pubkey_out, &out_len, &pk,
|
||||
fCompressed ? SECP256K1_EC_COMPRESSED
|
||||
: SECP256K1_EC_UNCOMPRESSED))
|
||||
return false;
|
||||
|
||||
*pubkey_len_out = out_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ECDSA_seckey_verify_secp256k1(const unsigned char privkey32[32])
|
||||
{
|
||||
secp256k1_context* ctx = GetEcdsaContext();
|
||||
if (!ctx) return false;
|
||||
return secp256k1_ec_seckey_verify(ctx, privkey32) == 1;
|
||||
}
|
||||
|
||||
bool ECDSA_pubkey_verify_secp256k1(const unsigned char* pubkey, std::size_t pubkey_len)
|
||||
{
|
||||
if (pubkey_len != 33 && pubkey_len != 65) return false;
|
||||
secp256k1_context* ctx = GetEcdsaContext();
|
||||
if (!ctx) return false;
|
||||
secp256k1_pubkey pk;
|
||||
return secp256k1_ec_pubkey_parse(ctx, &pk, pubkey, pubkey_len) == 1;
|
||||
}
|
||||
|
||||
bool ECDSA_pubkey_from_privkey_secp256k1(unsigned char* out, std::size_t* out_len_out,
|
||||
const unsigned char privkey32[32],
|
||||
bool fCompressed)
|
||||
{
|
||||
if (!out || !out_len_out) return false;
|
||||
secp256k1_context* ctx = GetEcdsaContext();
|
||||
if (!ctx) return false;
|
||||
|
||||
secp256k1_pubkey pk;
|
||||
if (!secp256k1_ec_pubkey_create(ctx, &pk, privkey32))
|
||||
return false;
|
||||
|
||||
std::size_t len = fCompressed ? 33 : 65;
|
||||
if (!secp256k1_ec_pubkey_serialize(ctx, out, &len, &pk,
|
||||
fCompressed ? SECP256K1_EC_COMPRESSED
|
||||
: SECP256K1_EC_UNCOMPRESSED))
|
||||
return false;
|
||||
*out_len_out = len;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// SEC1 / RFC-5915 DER codec for secp256k1 ECPrivateKey
|
||||
//
|
||||
// Vendored from Bitcoin Core (src/key.cpp), MIT-licensed. The decoder is lax
|
||||
// about details (matches OpenSSL's d2i_ECPrivateKey lenience); the encoder
|
||||
// writes the exact byte layout that OpenSSL's i2d_ECPrivateKey produces for
|
||||
// this curve so wallet.dat records remain interchangeable across versions.
|
||||
//
|
||||
// Compressed pubkey: 214 bytes
|
||||
// Uncompressed pubkey: 279 bytes
|
||||
//
|
||||
// The static templates below carry every byte except the 32-byte private
|
||||
// scalar and the public key bytes, which are spliced into the precomputed
|
||||
// offsets at encode time.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
namespace {
|
||||
|
||||
const unsigned char der_template_compressed[214] = {
|
||||
0x30,0x81,0xD3,0x02,0x01,0x01,0x04,0x20,
|
||||
/* private key (32 bytes) at offset 8 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0xA0,0x81,0x85,0x30,0x81,0x82,0x02,0x01,0x01,0x30,0x2C,0x06,0x07,0x2A,0x86,0x48,
|
||||
0xCE,0x3D,0x01,0x01,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFC,0x2F,0x30,0x06,0x04,0x01,0x00,
|
||||
0x04,0x01,0x07,0x04,0x21,0x02,0x79,0xBE,0x66,0x7E,0xF9,0xDC,0xBB,0xAC,0x55,0xA0,
|
||||
0x62,0x95,0xCE,0x87,0x0B,0x07,0x02,0x9B,0xFC,0xDB,0x2D,0xCE,0x28,0xD9,0x59,0xF2,
|
||||
0x81,0x5B,0x16,0xF8,0x17,0x98,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,
|
||||
0x3B,0xBF,0xD2,0x5E,0x8C,0xD0,0x36,0x41,0x41,0x02,0x01,0x01,0xA1,0x24,0x03,0x22,
|
||||
0x00,
|
||||
/* compressed pubkey (33 bytes) at offset 181 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
};
|
||||
|
||||
const unsigned char der_template_uncompressed[279] = {
|
||||
0x30,0x82,0x01,0x13,0x02,0x01,0x01,0x04,0x20,
|
||||
/* private key (32 bytes) at offset 9 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0xA0,0x81,0xA5,0x30,0x81,0xA2,0x02,0x01,0x01,0x30,0x2C,0x06,0x07,0x2A,0x86,0x48,
|
||||
0xCE,0x3D,0x01,0x01,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFC,0x2F,0x30,0x06,0x04,0x01,0x00,
|
||||
0x04,0x01,0x07,0x04,0x41,0x04,0x79,0xBE,0x66,0x7E,0xF9,0xDC,0xBB,0xAC,0x55,0xA0,
|
||||
0x62,0x95,0xCE,0x87,0x0B,0x07,0x02,0x9B,0xFC,0xDB,0x2D,0xCE,0x28,0xD9,0x59,0xF2,
|
||||
0x81,0x5B,0x16,0xF8,0x17,0x98,0x48,0x3A,0xDA,0x77,0x26,0xA3,0xC4,0x65,0x5D,0xA4,
|
||||
0xFB,0xFC,0x0E,0x11,0x08,0xA8,0xFD,0x17,0xB4,0x48,0xA6,0x85,0x54,0x19,0x9C,0x47,
|
||||
0xD0,0x8F,0xFB,0x10,0xD4,0xB8,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,
|
||||
0x3B,0xBF,0xD2,0x5E,0x8C,0xD0,0x36,0x41,0x41,0x02,0x01,0x01,0xA1,0x44,0x03,0x42,
|
||||
0x00,
|
||||
/* uncompressed pubkey (65 bytes) at offset 214 */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ECDSA_privkey_export_der_secp256k1(unsigned char* out, std::size_t* out_len_out,
|
||||
const unsigned char privkey32[32],
|
||||
bool fCompressed)
|
||||
{
|
||||
if (!out || !out_len_out) return false;
|
||||
|
||||
secp256k1_context* ctx = GetEcdsaContext();
|
||||
if (!ctx) return false;
|
||||
|
||||
secp256k1_pubkey pk;
|
||||
if (!secp256k1_ec_pubkey_create(ctx, &pk, privkey32))
|
||||
return false;
|
||||
|
||||
if (fCompressed) {
|
||||
std::memcpy(out, der_template_compressed, sizeof(der_template_compressed));
|
||||
std::memcpy(out + 8, privkey32, 32);
|
||||
std::size_t pub_len = 33;
|
||||
if (!secp256k1_ec_pubkey_serialize(ctx, out + 181, &pub_len, &pk, SECP256K1_EC_COMPRESSED))
|
||||
return false;
|
||||
*out_len_out = sizeof(der_template_compressed);
|
||||
} else {
|
||||
std::memcpy(out, der_template_uncompressed, sizeof(der_template_uncompressed));
|
||||
std::memcpy(out + 9, privkey32, 32);
|
||||
std::size_t pub_len = 65;
|
||||
if (!secp256k1_ec_pubkey_serialize(ctx, out + 214, &pub_len, &pk, SECP256K1_EC_UNCOMPRESSED))
|
||||
return false;
|
||||
*out_len_out = sizeof(der_template_uncompressed);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ECDSA_privkey_import_der_secp256k1(unsigned char privkey32_out[32],
|
||||
const unsigned char* der, std::size_t der_len)
|
||||
{
|
||||
// Lax SEC1/RFC-5915 ECPrivateKey parser. We only need to find the OCTET
|
||||
// STRING containing the private key scalar; everything else (curve params,
|
||||
// optional public key) is informational. Mirrors Bitcoin Core's
|
||||
// ec_privkey_import_der.
|
||||
const unsigned char* end = der + der_len;
|
||||
if (end < der + 1 || *(der++) != 0x30) return false;
|
||||
|
||||
// Outer SEQUENCE length — variable length encoding.
|
||||
if (der >= end) return false;
|
||||
int lenb = *(der++);
|
||||
if (lenb < 0x80) {
|
||||
// short form, ignore
|
||||
} else {
|
||||
int n = lenb & 0x7F;
|
||||
if (n == 0 || n > 2) return false;
|
||||
if (der + n > end) return false;
|
||||
der += n;
|
||||
}
|
||||
|
||||
// Version INTEGER (1).
|
||||
if (der + 3 > end || der[0] != 0x02 || der[1] != 0x01 || der[2] != 0x01) return false;
|
||||
der += 3;
|
||||
|
||||
// privateKey OCTET STRING (length 32).
|
||||
if (der + 2 > end || der[0] != 0x04 || der[1] != 0x20) return false;
|
||||
der += 2;
|
||||
if (der + 32 > end) return false;
|
||||
std::memcpy(privkey32_out, der, 32);
|
||||
|
||||
// Validate the result against the curve order; reject zero / >= n.
|
||||
return ECDSA_seckey_verify_secp256k1(privkey32_out);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2026 The Triangles developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#ifndef TRIANGLES_CRYPTO_ECDSA_H
|
||||
#define TRIANGLES_CRYPTO_ECDSA_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
/**
|
||||
* Verify a DER-encoded secp256k1 ECDSA signature using libsecp256k1.
|
||||
*
|
||||
* Drop-in replacement for OpenSSL's
|
||||
* ECDSA_verify(0, hash, 32, sig, sig_len, pkey)
|
||||
* with one important caveat baked in: the DER input is parsed *laxly*
|
||||
* (Bitcoin Core's `lax_der_parsing` algorithm), so historical non-canonical
|
||||
* encodings already on chain — extra padding, leading zeros, length-byte
|
||||
* quirks that OpenSSL's permissive ASN.1 reader once accepted — continue
|
||||
* to verify. Strict-DER-only parsing here would silently fork the chain.
|
||||
*
|
||||
* High-S signatures are accepted (libsecp256k1's verify behaviour by default).
|
||||
* No malleability check is applied; that is policy and lives elsewhere.
|
||||
*
|
||||
* @param hash32 32-byte message hash to verify against.
|
||||
* @param sig DER-encoded signature bytes.
|
||||
* @param sig_len Length of `sig`.
|
||||
* @param pubkey Serialized public key (33 bytes compressed or 65 uncompressed).
|
||||
* @param pubkey_len 33 or 65; any other length fails immediately.
|
||||
* @return true iff the signature is valid for (hash32, pubkey).
|
||||
*/
|
||||
bool ECDSA_verify_secp256k1(const unsigned char hash32[32],
|
||||
const unsigned char* sig, std::size_t sig_len,
|
||||
const unsigned char* pubkey, std::size_t pubkey_len);
|
||||
|
||||
/**
|
||||
* Sign `hash32` with `privkey32` and write a DER-encoded signature to `out`.
|
||||
*
|
||||
* libsecp256k1 uses RFC 6979 deterministic nonces, so signature bytes will
|
||||
* differ from OpenSSL's random-nonce output for the same key+hash, but any
|
||||
* resulting signature is equally valid. Low-S is enforced automatically.
|
||||
*
|
||||
* @param out Output buffer; must be at least `*out_len` bytes.
|
||||
* libsecp256k1 produces at most 72 bytes of DER.
|
||||
* @param out_len In: capacity of `out`. Out: bytes actually written.
|
||||
* @param hash32 32-byte message hash to sign.
|
||||
* @param privkey32 32-byte secret scalar.
|
||||
* @return true on success.
|
||||
*/
|
||||
bool ECDSA_sign_secp256k1(unsigned char* out, std::size_t* out_len,
|
||||
const unsigned char hash32[32],
|
||||
const unsigned char privkey32[32]);
|
||||
|
||||
/**
|
||||
* Produce a 65-byte recoverable compact signature.
|
||||
*
|
||||
* Output layout matches the existing wire format:
|
||||
* out[0] = 27 + recid + (fCompressed ? 4 : 0)
|
||||
* out[1..33) = R (big-endian, 32 bytes)
|
||||
* out[33..65) = S (big-endian, 32 bytes)
|
||||
*
|
||||
* @param out65 65-byte output buffer.
|
||||
* @param hash32 32-byte message hash to sign.
|
||||
* @param privkey32 32-byte secret scalar.
|
||||
* @param fCompressed Whether the matching public key is compressed; affects
|
||||
* the recid offset in the header byte.
|
||||
* @return true on success.
|
||||
*/
|
||||
bool ECDSA_sign_compact_secp256k1(unsigned char out65[65],
|
||||
const unsigned char hash32[32],
|
||||
const unsigned char privkey32[32],
|
||||
bool fCompressed);
|
||||
|
||||
/**
|
||||
* Recover the signing public key from a 65-byte compact signature (as produced
|
||||
* by ECDSA_sign_compact_secp256k1) and a message hash.
|
||||
*
|
||||
* The header byte's "compressed" flag determines whether the recovered key is
|
||||
* serialized as 33 bytes (compressed) or 65 bytes (uncompressed).
|
||||
*
|
||||
* @param pubkey_out Output buffer; needs at least 65 bytes capacity.
|
||||
* @param pubkey_len_out Receives the actual serialized length (33 or 65).
|
||||
* @param hash32 32-byte message hash that was signed.
|
||||
* @param sig65 65-byte compact signature.
|
||||
* @return true if recovery succeeded.
|
||||
*/
|
||||
bool ECDSA_recover_compact_secp256k1(unsigned char* pubkey_out,
|
||||
std::size_t* pubkey_len_out,
|
||||
const unsigned char hash32[32],
|
||||
const unsigned char sig65[65]);
|
||||
|
||||
/** Return true iff `privkey32` is a valid secp256k1 secret (in (0, n)). */
|
||||
bool ECDSA_seckey_verify_secp256k1(const unsigned char privkey32[32]);
|
||||
|
||||
/** Return true iff `pubkey/pubkey_len` parses as a valid secp256k1 point. */
|
||||
bool ECDSA_pubkey_verify_secp256k1(const unsigned char* pubkey, std::size_t pubkey_len);
|
||||
|
||||
/**
|
||||
* Derive the public key for `privkey32` and serialize it.
|
||||
* @param out Output buffer; must be at least 65 bytes.
|
||||
* @param out_len_out Receives the actual length (33 or 65).
|
||||
* @param privkey32 32-byte secret scalar.
|
||||
* @param fCompressed Whether to serialize compressed (33B) or uncompressed (65B).
|
||||
* @return true on success.
|
||||
*/
|
||||
bool ECDSA_pubkey_from_privkey_secp256k1(unsigned char* out, std::size_t* out_len_out,
|
||||
const unsigned char privkey32[32],
|
||||
bool fCompressed);
|
||||
|
||||
/**
|
||||
* SEC1/RFC-5915 DER ECPrivateKey encoder/decoder for the secp256k1 curve.
|
||||
* Output bytes match the layout produced by OpenSSL's i2d_ECPrivateKey on this
|
||||
* curve (compressed = 214 bytes, uncompressed = 279 bytes), so wallet.dat
|
||||
* records written by previous OpenSSL-EC builds remain readable, and records
|
||||
* we write remain readable by older OpenSSL-based builds.
|
||||
*/
|
||||
bool ECDSA_privkey_export_der_secp256k1(unsigned char* out, std::size_t* out_len_out,
|
||||
const unsigned char privkey32[32],
|
||||
bool fCompressed);
|
||||
bool ECDSA_privkey_import_der_secp256k1(unsigned char privkey32_out[32],
|
||||
const unsigned char* der, std::size_t der_len);
|
||||
|
||||
#endif // TRIANGLES_CRYPTO_ECDSA_H
|
||||
+22
-23
@@ -8,16 +8,15 @@
|
||||
#include "util.h"
|
||||
#include "main.h"
|
||||
#include "ui_interface.h"
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#ifndef WIN32
|
||||
#include "sys/stat.h"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace boost;
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
unsigned int nWalletDBUpdated;
|
||||
@@ -134,7 +133,7 @@ void CDBEnv::MakeMock()
|
||||
#ifdef DB_LOG_IN_MEMORY
|
||||
dbenv.log_set_config(DB_LOG_IN_MEMORY, 1);
|
||||
#endif
|
||||
int ret = dbenv.open(NULL,
|
||||
int ret = dbenv.open(nullptr,
|
||||
DB_CREATE |
|
||||
DB_INIT_LOCK |
|
||||
DB_INIT_LOG |
|
||||
@@ -156,10 +155,10 @@ CDBEnv::VerifyResult CDBEnv::Verify(std::string strFile, bool (*recoverFunc)(CDB
|
||||
assert(mapFileUseCount.count(strFile) == 0);
|
||||
|
||||
Db db(&dbenv, 0);
|
||||
int result = db.verify(strFile.c_str(), NULL, NULL, 0);
|
||||
int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
|
||||
if (result == 0)
|
||||
return VERIFY_OK;
|
||||
else if (recoverFunc == NULL)
|
||||
else if (recoverFunc == nullptr)
|
||||
return RECOVER_FAIL;
|
||||
|
||||
// Try to recover:
|
||||
@@ -179,7 +178,7 @@ bool CDBEnv::Salvage(std::string strFile, bool fAggressive,
|
||||
stringstream strDump;
|
||||
|
||||
Db db(&dbenv, 0);
|
||||
int result = db.verify(strFile.c_str(), NULL, &strDump, flags);
|
||||
int result = db.verify(strFile.c_str(), nullptr, &strDump, flags);
|
||||
if (result == DB_VERIFY_BAD)
|
||||
{
|
||||
printf("Error: Salvage found errors, all data may not be recoverable.\n");
|
||||
@@ -232,10 +231,10 @@ void CDBEnv::CheckpointLSN(std::string strFile)
|
||||
|
||||
|
||||
CDB::CDB(const char *pszFile, const char* pszMode) :
|
||||
pdb(NULL), activeTxn(NULL)
|
||||
pdb(nullptr), activeTxn(nullptr)
|
||||
{
|
||||
int ret;
|
||||
if (pszFile == NULL)
|
||||
if (pszFile == nullptr)
|
||||
return;
|
||||
|
||||
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
|
||||
@@ -252,7 +251,7 @@ CDB::CDB(const char *pszFile, const char* pszMode) :
|
||||
strFile = pszFile;
|
||||
++bitdb.mapFileUseCount[strFile];
|
||||
pdb = bitdb.mapDb[strFile];
|
||||
if (pdb == NULL)
|
||||
if (pdb == nullptr)
|
||||
{
|
||||
pdb = new Db(&bitdb.dbenv, 0);
|
||||
|
||||
@@ -265,8 +264,8 @@ CDB::CDB(const char *pszFile, const char* pszMode) :
|
||||
throw runtime_error(strprintf("CDB() : failed to configure for no temp file backing for database %s", pszFile));
|
||||
}
|
||||
|
||||
ret = pdb->open(NULL, // Txn pointer
|
||||
fMockDb ? NULL : pszFile, // Filename
|
||||
ret = pdb->open(nullptr, // Txn pointer
|
||||
fMockDb ? nullptr : pszFile, // Filename
|
||||
"main", // Logical db name
|
||||
DB_BTREE, // Database type
|
||||
nFlags, // Flags
|
||||
@@ -275,7 +274,7 @@ CDB::CDB(const char *pszFile, const char* pszMode) :
|
||||
if (ret != 0)
|
||||
{
|
||||
delete pdb;
|
||||
pdb = NULL;
|
||||
pdb = nullptr;
|
||||
--bitdb.mapFileUseCount[strFile];
|
||||
strFile = "";
|
||||
throw runtime_error(strprintf("CDB() : can't open database file %s, error %d", pszFile, ret));
|
||||
@@ -308,8 +307,8 @@ void CDB::Close()
|
||||
return;
|
||||
if (activeTxn)
|
||||
activeTxn->abort();
|
||||
activeTxn = NULL;
|
||||
pdb = NULL;
|
||||
activeTxn = nullptr;
|
||||
pdb = nullptr;
|
||||
|
||||
// Flush database activity from memory pool to disk log
|
||||
unsigned int nMinutes = 0;
|
||||
@@ -332,13 +331,13 @@ void CDBEnv::CloseDb(const string& strFile)
|
||||
{
|
||||
{
|
||||
LOCK(cs_db);
|
||||
if (mapDb[strFile] != NULL)
|
||||
if (mapDb[strFile] != nullptr)
|
||||
{
|
||||
// Close the database handle
|
||||
Db* pdb = mapDb[strFile];
|
||||
pdb->close(0);
|
||||
delete pdb;
|
||||
mapDb[strFile] = NULL;
|
||||
mapDb[strFile] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,7 +347,7 @@ bool CDBEnv::RemoveDb(const string& strFile)
|
||||
this->CloseDb(strFile);
|
||||
|
||||
LOCK(cs_db);
|
||||
int rc = dbenv.dbremove(NULL, strFile.c_str(), NULL, DB_AUTO_COMMIT);
|
||||
int rc = dbenv.dbremove(nullptr, strFile.c_str(), nullptr, DB_AUTO_COMMIT);
|
||||
return (rc == 0);
|
||||
}
|
||||
|
||||
@@ -372,7 +371,7 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip)
|
||||
CDB db(strFile.c_str(), "r");
|
||||
Db* pdbCopy = new Db(&bitdb.dbenv, 0);
|
||||
|
||||
int ret = pdbCopy->open(NULL, // Txn pointer
|
||||
int ret = pdbCopy->open(nullptr, // Txn pointer
|
||||
strFileRes.c_str(), // Filename
|
||||
"main", // Logical db name
|
||||
DB_BTREE, // Database type
|
||||
@@ -413,7 +412,7 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip)
|
||||
}
|
||||
Dbt datKey(&ssKey[0], ssKey.size());
|
||||
Dbt datValue(&ssValue[0], ssValue.size());
|
||||
int ret2 = pdbCopy->put(NULL, &datKey, &datValue, DB_NOOVERWRITE);
|
||||
int ret2 = pdbCopy->put(nullptr, &datKey, &datValue, DB_NOOVERWRITE);
|
||||
if (ret2 > 0)
|
||||
fSuccess = false;
|
||||
}
|
||||
@@ -429,10 +428,10 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip)
|
||||
if (fSuccess)
|
||||
{
|
||||
Db dbA(&bitdb.dbenv, 0);
|
||||
if (dbA.remove(strFile.c_str(), NULL, 0))
|
||||
if (dbA.remove(strFile.c_str(), nullptr, 0))
|
||||
fSuccess = false;
|
||||
Db dbB(&bitdb.dbenv, 0);
|
||||
if (dbB.rename(strFileRes.c_str(), NULL, strFile.c_str(), 0))
|
||||
if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(), 0))
|
||||
fSuccess = false;
|
||||
}
|
||||
if (!fSuccess)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "main.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -28,7 +29,7 @@ extern unsigned int nWalletDBUpdated;
|
||||
|
||||
void ThreadFlushWalletDB(void* parg);
|
||||
bool BackupWallet(const CWallet& wallet, const std::string& strDest);
|
||||
bool AutoBackupWallet(const boost::filesystem::path& walletPath);
|
||||
bool AutoBackupWallet(const std::filesystem::path& walletPath);
|
||||
|
||||
|
||||
class CDBEnv
|
||||
@@ -37,7 +38,7 @@ private:
|
||||
bool fDetachDB;
|
||||
bool fDbEnvInit;
|
||||
bool fMockDb;
|
||||
boost::filesystem::path pathEnv;
|
||||
std::filesystem::path pathEnv;
|
||||
std::string strPath;
|
||||
|
||||
void EnvShutdown();
|
||||
@@ -71,7 +72,7 @@ public:
|
||||
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
|
||||
bool Salvage(std::string strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
|
||||
|
||||
bool Open(boost::filesystem::path pathEnv_);
|
||||
bool Open(std::filesystem::path pathEnv_);
|
||||
void Close();
|
||||
void Flush(bool fShutdown);
|
||||
void CheckpointLSN(std::string strFile);
|
||||
@@ -83,10 +84,10 @@ public:
|
||||
|
||||
DbTxn *TxnBegin(int flags=DB_TXN_WRITE_NOSYNC)
|
||||
{
|
||||
DbTxn* ptxn = NULL;
|
||||
int ret = dbenv.txn_begin(NULL, &ptxn, flags);
|
||||
DbTxn* ptxn = nullptr;
|
||||
int ret = dbenv.txn_begin(nullptr, &ptxn, flags);
|
||||
if (!ptxn || ret != 0)
|
||||
return NULL;
|
||||
return nullptr;
|
||||
return ptxn;
|
||||
}
|
||||
};
|
||||
@@ -129,7 +130,7 @@ protected:
|
||||
datValue.set_flags(DB_DBT_MALLOC);
|
||||
int ret = pdb->get(activeTxn, &datKey, &datValue, 0);
|
||||
memset(datKey.get_data(), 0, datKey.get_size());
|
||||
if (datValue.get_data() == NULL)
|
||||
if (datValue.get_data() == nullptr)
|
||||
return false;
|
||||
|
||||
// Unserialize value
|
||||
@@ -221,11 +222,11 @@ protected:
|
||||
Dbc* GetCursor()
|
||||
{
|
||||
if (!pdb)
|
||||
return NULL;
|
||||
Dbc* pcursor = NULL;
|
||||
int ret = pdb->cursor(NULL, &pcursor, 0);
|
||||
return nullptr;
|
||||
Dbc* pcursor = nullptr;
|
||||
int ret = pdb->cursor(nullptr, &pcursor, 0);
|
||||
if (ret != 0)
|
||||
return NULL;
|
||||
return nullptr;
|
||||
return pcursor;
|
||||
}
|
||||
|
||||
@@ -249,7 +250,7 @@ protected:
|
||||
int ret = pcursor->get(&datKey, &datValue, fFlags);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
else if (datKey.get_data() == NULL || datValue.get_data() == NULL)
|
||||
else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr)
|
||||
return 99999;
|
||||
|
||||
// Convert to streams
|
||||
@@ -285,7 +286,7 @@ public:
|
||||
if (!pdb || !activeTxn)
|
||||
return false;
|
||||
int ret = activeTxn->commit(0);
|
||||
activeTxn = NULL;
|
||||
activeTxn = nullptr;
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
@@ -294,7 +295,7 @@ public:
|
||||
if (!pdb || !activeTxn)
|
||||
return false;
|
||||
int ret = activeTxn->abort();
|
||||
activeTxn = NULL;
|
||||
activeTxn = nullptr;
|
||||
return (ret == 0);
|
||||
}
|
||||
|
||||
@@ -309,7 +310,7 @@ public:
|
||||
return Write(std::string("version"), nVersion);
|
||||
}
|
||||
|
||||
bool static Rewrite(const std::string& strFile, const char* pszSkip = NULL);
|
||||
bool static Rewrite(const std::string& strFile, const char* pszSkip = nullptr);
|
||||
};
|
||||
|
||||
|
||||
@@ -317,7 +318,7 @@ public:
|
||||
class CAddrDB
|
||||
{
|
||||
private:
|
||||
boost::filesystem::path pathAddr;
|
||||
std::filesystem::path pathAddr;
|
||||
public:
|
||||
CAddrDB();
|
||||
bool Write(const CAddrMan& addr);
|
||||
|
||||
+142
-69
@@ -15,6 +15,7 @@
|
||||
#include "openssl_compat.h"
|
||||
#include "bootstrap.h"
|
||||
#include "utxosnapshot.h"
|
||||
#include "snapshotnet.h"
|
||||
#include "tor/tor_embedded.h"
|
||||
#include "tor/onion_v3.h"
|
||||
#include "tor/tor_process.h"
|
||||
@@ -23,37 +24,48 @@
|
||||
#endif
|
||||
#include "notificationqueue.h"
|
||||
#include "addressindex.h"
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
// boost/filesystem/convenience.hpp removed in modern Boost; functionality is in filesystem.hpp
|
||||
#include "chaindb_migrate.h"
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <boost/interprocess/sync/file_lock.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
#ifndef WIN32
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
// Windows.h (transitively included) defines these as macros, clobbering Checkpoints:: enum values.
|
||||
#ifdef STRICT
|
||||
#undef STRICT
|
||||
#endif
|
||||
#ifdef ADVISORY
|
||||
#undef ADVISORY
|
||||
#endif
|
||||
#ifdef PERMISSIVE
|
||||
#undef PERMISSIVE
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace boost;
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
CWallet* pwalletMain;
|
||||
std::unique_ptr<CWallet> pwalletMain;
|
||||
CClientUIInterface uiInterface;
|
||||
std::string strWalletFileName;
|
||||
bool fConfChange;
|
||||
bool fEnforceCanonical;
|
||||
unsigned int nNodeLifespan;
|
||||
unsigned int nDerivationMethodIndex;
|
||||
//unsigned int nMinerSleep;
|
||||
|
||||
bool fUseFastIndex;
|
||||
enum Checkpoints::CPMode CheckpointsMode;
|
||||
|
||||
static CCriticalSection cs_DeferredStartup;
|
||||
static bool fDeferredStartupRunning = false;
|
||||
static boost::thread_group* pScriptCheckThreads = NULL;
|
||||
static std::unique_ptr<std::vector<std::thread>> pScriptCheckThreads;
|
||||
|
||||
static void ThreadScriptCheck()
|
||||
{
|
||||
@@ -98,7 +110,7 @@ fRequestShutdown = true;
|
||||
uiInterface.QueueShutdown();
|
||||
#else
|
||||
// Without UI, Shutdown() can simply be started in a new thread
|
||||
NewThread(Shutdown, NULL);
|
||||
NewThread(Shutdown, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -108,6 +120,31 @@ bool ShutdownRequested()
|
||||
return fRequestShutdown;
|
||||
}
|
||||
|
||||
// P2P UTXO snapshot fetcher. Started from AppInit2 step 11.6 when the chain
|
||||
// is empty and snapshot mode is enabled. Saves utxo-snapshot.bin on success
|
||||
// and requests shutdown so a fresh boot can load it via Step 6c.
|
||||
static void ThreadSnapshotFetch(void* parg)
|
||||
{
|
||||
RenameThread("Triangles-snapfetch");
|
||||
// Give peers ~30s to connect and complete version handshake.
|
||||
for (int i = 0; i < 30 && !fRequestShutdown; ++i)
|
||||
MilliSleep(1000);
|
||||
if (fRequestShutdown) return;
|
||||
|
||||
int snapTimeoutSec = (int)GetArg("-snapshottimeout", 600);
|
||||
printf("SnapshotNet: starting P2P snapshot fetch (timeout=%ds)...\n", snapTimeoutSec);
|
||||
|
||||
std::string err;
|
||||
if (SnapshotNet::TryFetchSnapshot(GetDataDir(), snapTimeoutSec, err)) {
|
||||
printf("SnapshotNet: snapshot saved. Shutting down — restart the daemon to load it.\n");
|
||||
uiInterface.InitMessage(_("UTXO snapshot saved. Restart the node to load it."));
|
||||
StartShutdown();
|
||||
} else {
|
||||
printf("SnapshotNet: P2P snapshot fetch failed: %s\n", err.c_str());
|
||||
printf("SnapshotNet: falling back to genesis sync. Use -bootstrap for legacy HTTP fallback.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadDeferredStartup(void* parg)
|
||||
{
|
||||
// Make this thread recognisable as the deferred startup worker.
|
||||
@@ -142,7 +179,7 @@ void ThreadDeferredStartup(void* parg)
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
PrintExceptionContinue(NULL, "ThreadDeferredStartup()");
|
||||
PrintExceptionContinue(nullptr, "ThreadDeferredStartup()");
|
||||
}
|
||||
|
||||
{
|
||||
@@ -197,12 +234,11 @@ void Shutdown(void* parg)
|
||||
pScriptCheckQueue->Quit();
|
||||
if (pScriptCheckThreads)
|
||||
{
|
||||
pScriptCheckThreads->join_all();
|
||||
delete pScriptCheckThreads;
|
||||
pScriptCheckThreads = NULL;
|
||||
for (std::thread& t : *pScriptCheckThreads)
|
||||
if (t.joinable()) t.join();
|
||||
pScriptCheckThreads.reset();
|
||||
}
|
||||
delete pScriptCheckQueue;
|
||||
pScriptCheckQueue = NULL;
|
||||
pScriptCheckQueue.reset();
|
||||
}
|
||||
|
||||
// NOW safe to destroy Tor state - all threads have stopped
|
||||
@@ -214,24 +250,24 @@ void Shutdown(void* parg)
|
||||
{
|
||||
pzmqNotifier->Shutdown();
|
||||
delete pzmqNotifier;
|
||||
pzmqNotifier = NULL;
|
||||
pzmqNotifier = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pNotificationQueue)
|
||||
{
|
||||
delete pNotificationQueue;
|
||||
pNotificationQueue = NULL;
|
||||
pNotificationQueue = nullptr;
|
||||
}
|
||||
|
||||
// CTxDB().Close();
|
||||
// MakeChainDB()->Close();
|
||||
bitdb.Flush(false);
|
||||
bitdb.Flush(true);
|
||||
fs::remove(GetPidFile());
|
||||
UnregisterWallet(pwalletMain);
|
||||
delete pwalletMain;
|
||||
UnregisterWallet(pwalletMain.get());
|
||||
pwalletMain.reset();
|
||||
// DB is flushed and wallet saved - safe to force-exit if something hangs
|
||||
NewThread(ExitTimeout, NULL);
|
||||
NewThread(ExitTimeout, nullptr);
|
||||
MilliSleep(50);
|
||||
printf("Triangles exited\n\n");
|
||||
fExit = true;
|
||||
@@ -281,7 +317,7 @@ bool AppInit(int argc, char* argv[])
|
||||
if (!fs::is_directory(GetDataDir(false)))
|
||||
{
|
||||
fprintf(stderr, "Error: Specified directory does not exist\n");
|
||||
Shutdown(NULL);
|
||||
Shutdown(nullptr);
|
||||
}
|
||||
ReadConfigFile(mapArgs, mapMultiArgs);
|
||||
|
||||
@@ -303,7 +339,7 @@ bool AppInit(int argc, char* argv[])
|
||||
|
||||
// Command-line RPC
|
||||
for (int i = 1; i < argc; i++)
|
||||
if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "Triangles:"))
|
||||
if (!IsSwitchChar(argv[i][0]) && !std::equal(std::begin("Triangles:"), std::end("Triangles:") - 1, argv[i], [](char a, char b) { return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b)); }))
|
||||
fCommandLine = true;
|
||||
|
||||
if (fCommandLine)
|
||||
@@ -317,10 +353,10 @@ bool AppInit(int argc, char* argv[])
|
||||
catch (std::exception& e) {
|
||||
PrintException(&e, "AppInit()");
|
||||
} catch (...) {
|
||||
PrintException(NULL, "AppInit()");
|
||||
PrintException(nullptr, "AppInit()");
|
||||
}
|
||||
if (!fRet)
|
||||
Shutdown(NULL);
|
||||
Shutdown(nullptr);
|
||||
return fRet;
|
||||
}
|
||||
|
||||
@@ -443,7 +479,6 @@ std::string HelpMessage()
|
||||
" -walletnotify=<cmd> " + _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)") + "\n" +
|
||||
" -confchange " + _("Require a confirmations for change (default: 0)") + "\n" +
|
||||
" -enforcecanonical " + _("Enforce transaction scripts to use canonical PUSH operators (default: 1)") + "\n" +
|
||||
" -alertnotify=<cmd> " + _("Execute command when a relevant alert is received (%s in cmd is replaced by message)") + "\n" +
|
||||
" -upgradewallet " + _("Upgrade wallet to latest format") + "\n" +
|
||||
" -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n" +
|
||||
" -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" +
|
||||
@@ -506,7 +541,7 @@ bool AppInit2()
|
||||
#ifdef _MSC_VER
|
||||
// Turn off Microsoft heap dump noise
|
||||
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
|
||||
_CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0));
|
||||
_CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, 0));
|
||||
#endif
|
||||
#if _MSC_VER >= 1400
|
||||
// Disable confusing "helpful" text message on abort, Ctrl-C
|
||||
@@ -523,7 +558,7 @@ bool AppInit2()
|
||||
#endif
|
||||
typedef BOOL (WINAPI *PSETPROCDEPPOL)(DWORD);
|
||||
PSETPROCDEPPOL setProcDEPPol = (PSETPROCDEPPOL)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetProcessDEPPolicy");
|
||||
if (setProcDEPPol != NULL) setProcDEPPol(PROCESS_DEP_ENABLE);
|
||||
if (setProcDEPPol != nullptr) setProcDEPPol(PROCESS_DEP_ENABLE);
|
||||
#endif
|
||||
#ifndef WIN32
|
||||
umask(077);
|
||||
@@ -533,15 +568,15 @@ bool AppInit2()
|
||||
sa.sa_handler = HandleSIGTERM;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
|
||||
// Reopen debug.log on SIGHUP
|
||||
struct sigaction sa_hup;
|
||||
sa_hup.sa_handler = HandleSIGHUP;
|
||||
sigemptyset(&sa_hup.sa_mask);
|
||||
sa_hup.sa_flags = 0;
|
||||
sigaction(SIGHUP, &sa_hup, NULL);
|
||||
sigaction(SIGHUP, &sa_hup, nullptr);
|
||||
#endif
|
||||
|
||||
// ********************************************************* Step 2: parameter interactions
|
||||
@@ -551,7 +586,7 @@ bool AppInit2()
|
||||
//nMinerSleep = GetArg("-minersleep", 500);
|
||||
|
||||
CheckpointsMode = Checkpoints::STRICT;
|
||||
std::string strCpMode = GetArg("-cppolicy", "strict");
|
||||
std::string strCpMode = GetArg(std::string_view{"-cppolicy"}, std::string_view{"strict"});
|
||||
|
||||
if(strCpMode == "strict")
|
||||
CheckpointsMode = Checkpoints::STRICT;
|
||||
@@ -665,15 +700,15 @@ bool AppInit2()
|
||||
|
||||
int nScriptCheckThreads = GetArg("-par", 0);
|
||||
if (nScriptCheckThreads <= 0)
|
||||
nScriptCheckThreads = boost::thread::hardware_concurrency();
|
||||
nScriptCheckThreads = std::thread::hardware_concurrency();
|
||||
if (nScriptCheckThreads > 16)
|
||||
nScriptCheckThreads = 16;
|
||||
if (nScriptCheckThreads > 1)
|
||||
{
|
||||
pScriptCheckQueue = new CCheckQueue<CScriptCheck>(32);
|
||||
pScriptCheckThreads = new boost::thread_group();
|
||||
pScriptCheckQueue = std::make_unique<CCheckQueue<CScriptCheck>>(32);
|
||||
pScriptCheckThreads = std::make_unique<std::vector<std::thread>>();
|
||||
for (int i = 0; i < nScriptCheckThreads - 1; ++i)
|
||||
pScriptCheckThreads->create_thread(&ThreadScriptCheck);
|
||||
pScriptCheckThreads->emplace_back(&ThreadScriptCheck);
|
||||
printf("Script verification threads: %d workers + main thread\n", nScriptCheckThreads - 1);
|
||||
}
|
||||
|
||||
@@ -693,7 +728,7 @@ bool AppInit2()
|
||||
return InitError(_("Initialization sanity check failed. Triangles is shutting down."));
|
||||
|
||||
std::string strDataDir = GetDataDir().string();
|
||||
std::string strWalletFileName = GetArg("-wallet", "wallet.dat");
|
||||
std::string strWalletFileName = GetArg(std::string_view{"-wallet"}, std::string_view{"wallet.dat"});
|
||||
|
||||
// strWalletFileName must be a plain filename without a directory
|
||||
if (strWalletFileName != fs::path(strWalletFileName).stem().string() + fs::path(strWalletFileName).extension().string())
|
||||
@@ -877,7 +912,7 @@ bool AppInit2()
|
||||
|
||||
if (mapArgs.count("-checkpointkey")) // triangles: checkpoint master priv key
|
||||
{
|
||||
if (!Checkpoints::SetCheckpointPrivKey(GetArg("-checkpointkey", "")))
|
||||
if (!Checkpoints::SetCheckpointPrivKey(GetArg(std::string_view{"-checkpointkey"}, std::string_view{""})))
|
||||
InitError(_("Unable to sign checkpoint, wrong checkpointkey?\n"));
|
||||
}
|
||||
|
||||
@@ -888,17 +923,25 @@ bool AppInit2()
|
||||
// ********************************************************* Step 6b: bootstrap download (daemon)
|
||||
// Automatic: if data dir has no blockchain, bootstrap without asking.
|
||||
// Can also be forced with -bootstrap flag, or disabled with -nobootstrap.
|
||||
//
|
||||
// v5.9.5: P2P UTXO snapshot fetch is the default for fresh installs (Step 11.6).
|
||||
// The legacy clearnet HTTP bootstrap only runs when the user explicitly requests
|
||||
// it via -bootstrap, or when -snapshot=0 disables the P2P fetcher.
|
||||
#ifndef QT_GUI
|
||||
{
|
||||
bool wantsBootstrap = GetBoolArg("-bootstrap", false);
|
||||
bool noBootstrap = GetBoolArg("-nobootstrap", false);
|
||||
bool snapshotMode = GetBoolArg("-snapshot", true);
|
||||
fs::path dataPath = GetDataDir();
|
||||
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataPath);
|
||||
|
||||
if (needsBootstrap && !noBootstrap) {
|
||||
if (needsBootstrap && !noBootstrap && !snapshotMode) {
|
||||
printf("Bootstrap: no blockchain data found — downloading automatically.\n");
|
||||
printf("Bootstrap: (use -nobootstrap to skip)\n");
|
||||
wantsBootstrap = true;
|
||||
} else if (needsBootstrap && snapshotMode && !wantsBootstrap) {
|
||||
printf("Bootstrap: no blockchain data found — will fetch UTXO snapshot via P2P after network start.\n");
|
||||
printf("Bootstrap: (use -bootstrap for legacy clearnet HTTP bootstrap, or -snapshot=0 to disable P2P fetcher)\n");
|
||||
}
|
||||
|
||||
if (wantsBootstrap)
|
||||
@@ -918,10 +961,11 @@ bool AppInit2()
|
||||
}
|
||||
};
|
||||
|
||||
// Try UTXO snapshot first (fast: ~2-10 MB download)
|
||||
// Try UTXO snapshot first (fast: ~2-10 MB download). Only attempted
|
||||
// if the configured backend's chain DB doesn't already exist.
|
||||
bool success = false;
|
||||
bool triedUtxoSnapshot = false;
|
||||
if (needsBootstrap && !fs::exists(dataPath / "txleveldb")) {
|
||||
if (needsBootstrap && !fs::exists(GetChainDataDir())) {
|
||||
uiInterface.InitMessage(_("Downloading UTXO snapshot..."));
|
||||
printf("Bootstrap: trying UTXO snapshot from %s (fast path)...\n", host.c_str());
|
||||
|
||||
@@ -958,13 +1002,14 @@ bool AppInit2()
|
||||
#endif
|
||||
|
||||
// ********************************************************* Step 6c: manual UTXO snapshot loading
|
||||
// If utxo-snapshot.bin exists in data dir and no txleveldb, load it.
|
||||
// If utxo-snapshot.bin exists in data dir and the chain DB hasn't been
|
||||
// initialized for the configured backend, load it.
|
||||
{
|
||||
fs::path dataPath = GetDataDir();
|
||||
fs::path snapshotFile = dataPath / "utxo-snapshot.bin";
|
||||
fs::path txleveldbDir = dataPath / "txleveldb";
|
||||
fs::path chainDbDir = GetChainDataDir();
|
||||
|
||||
if (fs::exists(snapshotFile) && !fs::exists(txleveldbDir)) {
|
||||
if (fs::exists(snapshotFile) && !fs::exists(chainDbDir)) {
|
||||
printf("Found utxo-snapshot.bin — loading UTXO snapshot...\n");
|
||||
uiInterface.InitMessage(_("Loading UTXO snapshot..."));
|
||||
|
||||
@@ -978,6 +1023,16 @@ bool AppInit2()
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************* Step 6d: optional LevelDB -> RocksDB chain DB migration
|
||||
if (GetBoolArg("-migratechaindb", false) || GetBoolArg("-migratechaindbforce", false))
|
||||
{
|
||||
uiInterface.InitMessage(_("Migrating chain database to RocksDB..."));
|
||||
std::string strMigrateError;
|
||||
bool fForce = GetBoolArg("-migratechaindbforce", false);
|
||||
if (!MaybeMigrateLevelDbToRocksDb(fForce, strMigrateError))
|
||||
return InitError(strprintf(_("Chain DB migration failed: %s"), strMigrateError.c_str()));
|
||||
}
|
||||
|
||||
// ********************************************************* Step 7: load blockchain
|
||||
|
||||
if (!bitdb.Open(GetDataDir()))
|
||||
@@ -990,22 +1045,22 @@ bool AppInit2()
|
||||
|
||||
if (GetBoolArg("-loadblockindextest"))
|
||||
{
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
txdb.LoadBlockIndex();
|
||||
PrintBlockTree();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle -reindex: delete the LevelDB block index so it gets rebuilt
|
||||
// from the raw blk*.dat files via FastImportBlockFile().
|
||||
// This recalculates money supply, tx index, and UTXO set from scratch.
|
||||
// Handle -reindex: delete the chain DB so it gets rebuilt from the raw
|
||||
// blk*.dat files via FastImportBlockFile(). This recalculates money
|
||||
// supply, tx index, and UTXO set from scratch. Backend-agnostic via
|
||||
// WipeChainDataDir(), which resolves the directory per the configured
|
||||
// -chaindb backend.
|
||||
if (GetBoolArg("-reindex", false))
|
||||
{
|
||||
printf("Reindex requested: removing block index database...\n");
|
||||
uiInterface.InitMessage(_("Removing block index for reindex..."));
|
||||
fs::path txleveldbPath = GetDataDir() / "txleveldb";
|
||||
if (fs::exists(txleveldbPath))
|
||||
fs::remove_all(txleveldbPath);
|
||||
printf("Reindex requested: removing chain database...\n");
|
||||
uiInterface.InitMessage(_("Removing chain database for reindex..."));
|
||||
WipeChainDataDir();
|
||||
}
|
||||
|
||||
uiInterface.InitMessage(_("Loading block index..."));
|
||||
@@ -1017,7 +1072,7 @@ bool AppInit2()
|
||||
// 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")
|
||||
if (nBestHeight == 0 && std::filesystem::exists(GetDataDir() / "blk0001.dat")
|
||||
&& mapBlockIndex.size() <= 1)
|
||||
{
|
||||
uiInterface.InitMessage(_("Importing bootstrap blocks..."));
|
||||
@@ -1096,7 +1151,7 @@ bool AppInit2()
|
||||
printf("Loading wallet...\n");
|
||||
nStart = GetTimeMillis();
|
||||
bool fFirstRun = true;
|
||||
pwalletMain = new CWallet(strWalletFileName);
|
||||
pwalletMain = std::make_unique<CWallet>(strWalletFileName);
|
||||
|
||||
// Auto-backup wallet.dat before loading (protects against corruption during load/flush)
|
||||
{
|
||||
@@ -1140,9 +1195,9 @@ bool AppInit2()
|
||||
int nMaxVersion = GetArg("-upgradewallet", 0);
|
||||
if (nMaxVersion == 0) // the -upgradewallet without argument case
|
||||
{
|
||||
printf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
|
||||
printf("Performing wallet upgrade to %i\n", static_cast<int>(WalletFeature::Latest));
|
||||
nMaxVersion = CLIENT_VERSION;
|
||||
pwalletMain->SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately
|
||||
pwalletMain->SetMinVersion(WalletFeature::Latest); // permanently upgrade the wallet immediately
|
||||
}
|
||||
else
|
||||
printf("Allowing wallet upgrade up to %i\n", nMaxVersion);
|
||||
@@ -1168,7 +1223,7 @@ bool AppInit2()
|
||||
printf(" wallet %15" PRId64 "ms\n", GetTimeMillis() - nStart);
|
||||
StartupPerfLog("wallet_load", GetTimeMillis() - nStart, strprintf("firstrun=%d", fFirstRun));
|
||||
|
||||
RegisterWallet(pwalletMain);
|
||||
RegisterWallet(pwalletMain.get());
|
||||
|
||||
CBlockIndex *pindexRescan = pindexBest;
|
||||
if (GetBoolArg("-rescan"))
|
||||
@@ -1190,7 +1245,7 @@ bool AppInit2()
|
||||
bool fScannedWithIndex = false;
|
||||
if (fAddressIndex && !GetBoolArg("-rescan"))
|
||||
{
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
int nAddressIndexStartHeight = 0;
|
||||
uint256 hashAddressIndexBestChain = 0;
|
||||
if (txdb.ReadAddressIndexStartHeight(nAddressIndexStartHeight) &&
|
||||
@@ -1358,7 +1413,7 @@ bool AppInit2()
|
||||
|
||||
// Launch background thread for Tor health monitoring and seeder maintenance
|
||||
if (torStarted) {
|
||||
if (!NewThread(ThreadTorMaintenance, NULL))
|
||||
if (!NewThread(ThreadTorMaintenance, nullptr))
|
||||
printf("Warning: ThreadTorMaintenance could not be started\n");
|
||||
}
|
||||
}
|
||||
@@ -1426,31 +1481,49 @@ bool AppInit2()
|
||||
printf("mapWallet.size() = %" PRIszu "\n", pwalletMain->mapWallet.size());
|
||||
printf("mapAddressBook.size() = %" PRIszu "\n", pwalletMain->mapAddressBook.size());
|
||||
|
||||
if (!NewThread(StartNode, NULL))
|
||||
if (!NewThread(StartNode, nullptr))
|
||||
InitError(_("Error: could not start node"));
|
||||
|
||||
if (fServer)
|
||||
NewThread(ThreadRPCServer, NULL);
|
||||
NewThread(ThreadRPCServer, nullptr);
|
||||
|
||||
// ********************************************************* Step 11.6: P2P UTXO snapshot fetch
|
||||
// If the chain is empty and snapshot mode is enabled (default), spawn a
|
||||
// background thread that waits for snapshot-capable peers, downloads the
|
||||
// canonical snapshot via P2P, and saves it to utxo-snapshot.bin. On
|
||||
// success, requests a clean shutdown so the user can restart and have
|
||||
// Step 6c load the snapshot in a fresh boot.
|
||||
{
|
||||
bool snapshotMode = GetBoolArg("-snapshot", true);
|
||||
bool needsSnapshot = (nBestHeight <= 0);
|
||||
bool haveSnapshotFile = fs::exists(GetDataDir() / "utxo-snapshot.bin");
|
||||
|
||||
if (snapshotMode && needsSnapshot && !haveSnapshotFile &&
|
||||
Checkpoints::GetBestSnapshotHeight() > 0)
|
||||
{
|
||||
NewThread(ThreadSnapshotFetch, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
LOCK(cs_DeferredStartup);
|
||||
fDeferredStartupRunning = true;
|
||||
}
|
||||
if (!NewThread(ThreadDeferredStartup, NULL))
|
||||
if (!NewThread(ThreadDeferredStartup, nullptr))
|
||||
{
|
||||
printf("Warning: deferred startup thread could not be started, running inline\n");
|
||||
ThreadDeferredStartup(NULL);
|
||||
ThreadDeferredStartup(nullptr);
|
||||
}
|
||||
StartupPerfLog("start_services", GetTimeMillis() - nStart);
|
||||
|
||||
// ********************************************************* Step 11.5: ZMQ notifications
|
||||
#ifdef ENABLE_ZMQ
|
||||
{
|
||||
std::string zmqAddr = GetArg("-zmqpubhashblock", "");
|
||||
std::string zmqAddr = GetArg(std::string_view{"-zmqpubhashblock"}, std::string_view{""});
|
||||
if (zmqAddr.empty())
|
||||
zmqAddr = GetArg("-zmqpubhashtx", "");
|
||||
zmqAddr = GetArg(std::string_view{"-zmqpubhashtx"}, std::string_view{""});
|
||||
if (zmqAddr.empty())
|
||||
zmqAddr = GetArg("-zmqpub", "");
|
||||
zmqAddr = GetArg(std::string_view{"-zmqpub"}, std::string_view{""});
|
||||
if (!zmqAddr.empty())
|
||||
{
|
||||
pzmqNotifier = new CZMQPublishNotifier();
|
||||
@@ -1458,7 +1531,7 @@ bool AppInit2()
|
||||
{
|
||||
printf("ZMQ: Failed to initialize publisher on %s\n", zmqAddr.c_str());
|
||||
delete pzmqNotifier;
|
||||
pzmqNotifier = NULL;
|
||||
pzmqNotifier = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -7,8 +7,9 @@
|
||||
|
||||
#include "wallet.h"
|
||||
#include "tor_embed_hooks.h"
|
||||
#include <memory>
|
||||
|
||||
extern CWallet* pwalletMain;
|
||||
extern std::unique_ptr<CWallet> pwalletMain;
|
||||
extern std::string strWalletFileName;
|
||||
void StartShutdown();
|
||||
bool ShutdownRequested();
|
||||
|
||||
-405
@@ -1,405 +0,0 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2012 The Bitcoin developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "irc.h"
|
||||
#include "net.h"
|
||||
#include "strlcpy.h"
|
||||
#include "base58.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace boost;
|
||||
|
||||
int nGotIRCAddresses = 0;
|
||||
|
||||
void ThreadIRCSeed2(void* parg);
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct ircaddr
|
||||
{
|
||||
struct in_addr ip;
|
||||
short port;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
string EncodeAddress(const CService& addr)
|
||||
{
|
||||
struct ircaddr tmp;
|
||||
if (addr.GetInAddr(&tmp.ip))
|
||||
{
|
||||
tmp.port = htons(addr.GetPort());
|
||||
|
||||
vector<unsigned char> vch(UBEGIN(tmp), UEND(tmp));
|
||||
return string("u") + EncodeBase58Check(vch);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool DecodeAddress(string str, CService& addr)
|
||||
{
|
||||
vector<unsigned char> vch;
|
||||
if (!DecodeBase58Check(str.substr(1), vch))
|
||||
return false;
|
||||
|
||||
struct ircaddr tmp;
|
||||
if (vch.size() != sizeof(tmp))
|
||||
return false;
|
||||
memcpy(&tmp, &vch[0], sizeof(tmp));
|
||||
|
||||
addr = CService(tmp.ip, ntohs(tmp.port));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static bool Send(SOCKET hSocket, const char* pszSend)
|
||||
{
|
||||
if (strstr(pszSend, "PONG") != pszSend)
|
||||
printf("IRC SENDING: %s\n", pszSend);
|
||||
const char* psz = pszSend;
|
||||
const char* pszEnd = psz + strlen(psz);
|
||||
while (psz < pszEnd)
|
||||
{
|
||||
int ret = send(hSocket, psz, pszEnd - psz, MSG_NOSIGNAL);
|
||||
if (ret < 0)
|
||||
return false;
|
||||
psz += ret;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecvLineIRC(SOCKET hSocket, string& strLine)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
bool fRet = RecvLine(hSocket, strLine);
|
||||
if (fRet)
|
||||
{
|
||||
if (fShutdown)
|
||||
return false;
|
||||
vector<string> vWords;
|
||||
ParseString(strLine, ' ', vWords);
|
||||
if (vWords.size() >= 1 && vWords[0] == "PING")
|
||||
{
|
||||
strLine[1] = 'O';
|
||||
strLine += '\r';
|
||||
Send(hSocket, strLine.c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return fRet;
|
||||
}
|
||||
}
|
||||
|
||||
int RecvUntil(SOCKET hSocket, const char* psz1, const char* psz2=NULL, const char* psz3=NULL, const char* psz4=NULL)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
string strLine;
|
||||
strLine.reserve(10000);
|
||||
if (!RecvLineIRC(hSocket, strLine))
|
||||
return 0;
|
||||
printf("IRC %s\n", strLine.c_str());
|
||||
if (psz1 && strLine.find(psz1) != string::npos)
|
||||
return 1;
|
||||
if (psz2 && strLine.find(psz2) != string::npos)
|
||||
return 2;
|
||||
if (psz3 && strLine.find(psz3) != string::npos)
|
||||
return 3;
|
||||
if (psz4 && strLine.find(psz4) != string::npos)
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
bool Wait(int nSeconds)
|
||||
{
|
||||
if (fShutdown)
|
||||
return false;
|
||||
printf("IRC waiting %d seconds to reconnect\n", nSeconds);
|
||||
for (int i = 0; i < nSeconds; i++)
|
||||
{
|
||||
if (fShutdown)
|
||||
return false;
|
||||
MilliSleep(1000);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecvCodeLine(SOCKET hSocket, const char* psz1, string& strRet)
|
||||
{
|
||||
strRet.clear();
|
||||
while (true)
|
||||
{
|
||||
string strLine;
|
||||
if (!RecvLineIRC(hSocket, strLine))
|
||||
return false;
|
||||
|
||||
vector<string> vWords;
|
||||
ParseString(strLine, ' ', vWords);
|
||||
if (vWords.size() < 2)
|
||||
continue;
|
||||
|
||||
if (vWords[1] == psz1)
|
||||
{
|
||||
printf("IRC %s\n", strLine.c_str());
|
||||
strRet = strLine;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GetIPFromIRC(SOCKET hSocket, string strMyName, CNetAddr& ipRet)
|
||||
{
|
||||
Send(hSocket, strprintf("USERHOST %s\r", strMyName.c_str()).c_str());
|
||||
|
||||
string strLine;
|
||||
if (!RecvCodeLine(hSocket, "302", strLine))
|
||||
return false;
|
||||
|
||||
vector<string> vWords;
|
||||
ParseString(strLine, ' ', vWords);
|
||||
if (vWords.size() < 4)
|
||||
return false;
|
||||
|
||||
string str = vWords[3];
|
||||
if (str.rfind("@") == string::npos)
|
||||
return false;
|
||||
string strHost = str.substr(str.rfind("@")+1);
|
||||
|
||||
// Hybrid IRC used by lfnet always returns IP when you userhost yourself,
|
||||
// but in case another IRC is ever used this should work.
|
||||
printf("GetIPFromIRC() got userhost %s\n", strHost.c_str());
|
||||
CNetAddr addr(strHost, true);
|
||||
if (!addr.IsValid())
|
||||
return false;
|
||||
ipRet = addr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ThreadIRCSeed(void* parg)
|
||||
{
|
||||
// Make this thread recognisable as the IRC seeding thread
|
||||
RenameThread("Triangles-ircseed");
|
||||
|
||||
try
|
||||
{
|
||||
ThreadIRCSeed2(parg);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
PrintExceptionContinue(&e, "ThreadIRCSeed()");
|
||||
} catch (...) {
|
||||
PrintExceptionContinue(NULL, "ThreadIRCSeed()");
|
||||
}
|
||||
printf("ThreadIRCSeed exited\n");
|
||||
}
|
||||
|
||||
void ThreadIRCSeed2(void* parg)
|
||||
{
|
||||
// Don't connect to IRC if we won't use IPv4 connections.
|
||||
if (IsLimited(NET_IPV4))
|
||||
return;
|
||||
|
||||
// ... or if we won't make outbound connections and won't accept inbound ones.
|
||||
if (mapArgs.count("-connect") && fNoListen)
|
||||
return;
|
||||
|
||||
// ... or if IRC is not enabled.
|
||||
if (!GetBoolArg("-irc", false))
|
||||
return;
|
||||
|
||||
printf("ThreadIRCSeed started\n");
|
||||
int nErrorWait = 10;
|
||||
int nRetryWait = 10;
|
||||
int nNameRetry = 0;
|
||||
|
||||
while (!fShutdown)
|
||||
{
|
||||
CService addrConnect("92.243.23.21", 6667); // irc.lfnet.org
|
||||
|
||||
CService addrIRC("irc.lfnet.org", 6667, true);
|
||||
if (addrIRC.IsValid())
|
||||
addrConnect = addrIRC;
|
||||
|
||||
SOCKET hSocket;
|
||||
if (!ConnectSocket(addrConnect, hSocket))
|
||||
{
|
||||
printf("IRC connect failed\n");
|
||||
nErrorWait = nErrorWait * 11 / 10;
|
||||
if (Wait(nErrorWait += 60))
|
||||
continue;
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RecvUntil(hSocket, "Found your hostname", "using your IP address instead", "Couldn't look up your hostname", "ignoring hostname"))
|
||||
{
|
||||
closesocket(hSocket);
|
||||
hSocket = INVALID_SOCKET;
|
||||
nErrorWait = nErrorWait * 11 / 10;
|
||||
if (Wait(nErrorWait += 60))
|
||||
continue;
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
CNetAddr addrIPv4("1.2.3.4"); // arbitrary IPv4 address to make GetLocal prefer IPv4 addresses
|
||||
CService addrLocal;
|
||||
string strMyName;
|
||||
// Don't use our IP as our nick if we're not listening
|
||||
// or if it keeps failing because the nick is already in use.
|
||||
if (!fNoListen && GetLocal(addrLocal, &addrIPv4) && nNameRetry<3)
|
||||
strMyName = EncodeAddress(GetLocalAddress(&addrConnect));
|
||||
if (strMyName == "")
|
||||
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());
|
||||
|
||||
int nRet = RecvUntil(hSocket, " 004 ", " 433 ");
|
||||
if (nRet != 1)
|
||||
{
|
||||
closesocket(hSocket);
|
||||
hSocket = INVALID_SOCKET;
|
||||
if (nRet == 2)
|
||||
{
|
||||
printf("IRC name already in use\n");
|
||||
nNameRetry++;
|
||||
Wait(10);
|
||||
continue;
|
||||
}
|
||||
nErrorWait = nErrorWait * 11 / 10;
|
||||
if (Wait(nErrorWait += 60))
|
||||
continue;
|
||||
else
|
||||
return;
|
||||
}
|
||||
nNameRetry = 0;
|
||||
MilliSleep(500);
|
||||
|
||||
// Get our external IP from the IRC server and re-nick before joining the channel
|
||||
CNetAddr addrFromIRC;
|
||||
if (GetIPFromIRC(hSocket, strMyName, addrFromIRC))
|
||||
{
|
||||
printf("GetIPFromIRC() returned %s\n", addrFromIRC.ToString().c_str());
|
||||
// Don't use our IP as our nick if we're not listening
|
||||
if (!fNoListen && addrFromIRC.IsRoutable())
|
||||
{
|
||||
// IRC lets you to re-nick
|
||||
AddLocal(addrFromIRC, LOCAL_IRC);
|
||||
strMyName = EncodeAddress(GetLocalAddress(&addrConnect));
|
||||
Send(hSocket, strprintf("NICK %s\r", strMyName.c_str()).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (fTestNet) {
|
||||
Send(hSocket, "JOIN #TrianglesTEST\r");
|
||||
Send(hSocket, "WHO #TrianglesTEST\r");
|
||||
} else {
|
||||
// randomly join
|
||||
// int channel_number = GetRandInt(5);
|
||||
|
||||
// Channel number is always 0 for initial release
|
||||
int channel_number = 0;
|
||||
Send(hSocket, strprintf("JOIN #Triangles%02d\r", channel_number).c_str());
|
||||
Send(hSocket, strprintf("WHO #Triangles%02d\r", channel_number).c_str());
|
||||
}
|
||||
|
||||
int64_t nStart = GetTime();
|
||||
string strLine;
|
||||
strLine.reserve(10000);
|
||||
while (!fShutdown && RecvLineIRC(hSocket, strLine))
|
||||
{
|
||||
if (strLine.empty() || strLine.size() > 900 || strLine[0] != ':')
|
||||
continue;
|
||||
|
||||
vector<string> vWords;
|
||||
ParseString(strLine, ' ', vWords);
|
||||
if (vWords.size() < 2)
|
||||
continue;
|
||||
|
||||
char pszName[10000];
|
||||
pszName[0] = '\0';
|
||||
|
||||
if (vWords[1] == "352" && vWords.size() >= 8)
|
||||
{
|
||||
// index 7 is limited to 16 characters
|
||||
// could get full length name at index 10, but would be different from join messages
|
||||
strlcpy(pszName, vWords[7].c_str(), sizeof(pszName));
|
||||
printf("IRC got who\n");
|
||||
}
|
||||
|
||||
if (vWords[1] == "JOIN" && vWords[0].size() > 1)
|
||||
{
|
||||
// :username!username@50000007.F000000B.90000002.IP JOIN :#channelname
|
||||
strlcpy(pszName, vWords[0].c_str() + 1, sizeof(pszName));
|
||||
if (strchr(pszName, '!'))
|
||||
*strchr(pszName, '!') = '\0';
|
||||
printf("IRC got join\n");
|
||||
}
|
||||
|
||||
if (pszName[0] == 'u')
|
||||
{
|
||||
CAddress addr;
|
||||
if (DecodeAddress(pszName, addr))
|
||||
{
|
||||
addr.nTime = GetAdjustedTime();
|
||||
if (addrman.Add(addr, addrConnect, 51 * 60))
|
||||
printf("IRC got new address: %s\n", addr.ToString().c_str());
|
||||
nGotIRCAddresses++;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("IRC decode failed\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
closesocket(hSocket);
|
||||
hSocket = INVALID_SOCKET;
|
||||
|
||||
if (GetTime() - nStart > 20 * 60)
|
||||
{
|
||||
nErrorWait /= 3;
|
||||
nRetryWait /= 3;
|
||||
}
|
||||
|
||||
nRetryWait = nRetryWait * 11 / 10;
|
||||
if (!Wait(nRetryWait += 60))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef TEST
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
WSADATA wsadata;
|
||||
if (WSAStartup(MAKEWORD(2,2), &wsadata) != NO_ERROR)
|
||||
{
|
||||
printf("Error at WSAStartup()\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ThreadIRCSeed(NULL);
|
||||
|
||||
WSACleanup();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2012 The Bitcoin developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#ifndef TRIANGLES_IRC_H
|
||||
#define TRIANGLES_IRC_H
|
||||
|
||||
void ThreadIRCSeed(void* parg);
|
||||
|
||||
extern int nGotIRCAddresses;
|
||||
|
||||
#endif
|
||||
+2
-2
@@ -14,7 +14,7 @@ extern unsigned int nTargetSpacing;
|
||||
// Set to 20-minute for production network
|
||||
//unsigned int nModifierInterval = MODIFIER_INTERVAL;
|
||||
|
||||
typedef std::map<int, unsigned int> MapModifierCheckpoints;
|
||||
using MapModifierCheckpoints = std::map<int, unsigned int>;
|
||||
|
||||
// Hard checkpoints of stake modifiers to ensure they are deterministic
|
||||
static std::map<int, unsigned int> mapStakeModifierCheckpoints = {
|
||||
@@ -386,7 +386,7 @@ bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hash
|
||||
const CTxIn& txin = tx.vin[0];
|
||||
|
||||
// First try finding the previous transaction in database
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
CTransaction txPrev;
|
||||
CTxIndex txindex;
|
||||
if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
|
||||
|
||||
+272
-397
@@ -1,213 +1,33 @@
|
||||
// Copyright (c) 2009-2012 The Bitcoin developers
|
||||
// Copyright (c) 2026 The Triangles developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <map>
|
||||
#include <cstring>
|
||||
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/obj_mac.h>
|
||||
#include <openssl/crypto.h> // OPENSSL_cleanse for secure wipe of secret bytes
|
||||
#include <openssl/rand.h> // RAND_bytes for new-key entropy
|
||||
|
||||
#include "crypto_ecdsa.h"
|
||||
#include "key.h"
|
||||
|
||||
// Generate a private key from just the secret parameter
|
||||
int EC_KEY_regenerate_key(EC_KEY *eckey, BIGNUM *priv_key)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Order-of-generator constants (still used by CheckSignatureElement, the only
|
||||
// caller into the BigEndian comparison helper below). Kept here so the file
|
||||
// remains self-contained.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
namespace {
|
||||
|
||||
int CompareBigEndian(const unsigned char* c1, std::size_t c1len,
|
||||
const unsigned char* c2, std::size_t c2len)
|
||||
{
|
||||
int ok = 0;
|
||||
BN_CTX *ctx = NULL;
|
||||
EC_POINT *pub_key = NULL;
|
||||
|
||||
if (!eckey) return 0;
|
||||
|
||||
const EC_GROUP *group = EC_KEY_get0_group(eckey);
|
||||
|
||||
if ((ctx = BN_CTX_new()) == NULL)
|
||||
goto err;
|
||||
|
||||
pub_key = EC_POINT_new(group);
|
||||
|
||||
if (pub_key == NULL)
|
||||
goto err;
|
||||
|
||||
if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx))
|
||||
goto err;
|
||||
|
||||
EC_KEY_set_private_key(eckey,priv_key);
|
||||
EC_KEY_set_public_key(eckey,pub_key);
|
||||
|
||||
ok = 1;
|
||||
|
||||
err:
|
||||
|
||||
if (pub_key)
|
||||
EC_POINT_free(pub_key);
|
||||
if (ctx != NULL)
|
||||
BN_CTX_free(ctx);
|
||||
|
||||
return(ok);
|
||||
}
|
||||
|
||||
// Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields
|
||||
// recid selects which key is recovered
|
||||
// if check is non-zero, additional checks are performed
|
||||
int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check)
|
||||
{
|
||||
if (!eckey) return 0;
|
||||
|
||||
int ret = 0;
|
||||
BN_CTX *ctx = NULL;
|
||||
|
||||
BIGNUM *x = NULL;
|
||||
BIGNUM *e = NULL;
|
||||
BIGNUM *order = NULL;
|
||||
BIGNUM *sor = NULL;
|
||||
BIGNUM *eor = NULL;
|
||||
BIGNUM *field = NULL;
|
||||
EC_POINT *R = NULL;
|
||||
EC_POINT *O = NULL;
|
||||
EC_POINT *Q = NULL;
|
||||
BIGNUM *rr = NULL;
|
||||
BIGNUM *zero = NULL;
|
||||
int n = 0;
|
||||
int i = recid / 2;
|
||||
|
||||
const EC_GROUP *group = EC_KEY_get0_group(eckey);
|
||||
if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; }
|
||||
BN_CTX_start(ctx);
|
||||
const BIGNUM *sig_r, *sig_s;
|
||||
ECDSA_SIG_get0(ecsig, &sig_r, &sig_s);
|
||||
order = BN_CTX_get(ctx);
|
||||
if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; }
|
||||
x = BN_CTX_get(ctx);
|
||||
if (!BN_copy(x, order)) { ret=-1; goto err; }
|
||||
if (!BN_mul_word(x, i)) { ret=-1; goto err; }
|
||||
if (!BN_add(x, x, sig_r)) { ret=-1; goto err; }
|
||||
field = BN_CTX_get(ctx);
|
||||
if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; }
|
||||
if (BN_cmp(x, field) >= 0) { ret=0; goto err; }
|
||||
if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
|
||||
if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; }
|
||||
if (check)
|
||||
{
|
||||
if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
|
||||
if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; }
|
||||
if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; }
|
||||
}
|
||||
if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; }
|
||||
n = EC_GROUP_get_degree(group);
|
||||
e = BN_CTX_get(ctx);
|
||||
if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; }
|
||||
if (8*msglen > n) BN_rshift(e, e, 8-(n & 7));
|
||||
zero = BN_CTX_get(ctx);
|
||||
BN_zero(zero);
|
||||
if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; }
|
||||
rr = BN_CTX_get(ctx);
|
||||
if (!BN_mod_inverse(rr, sig_r, order, ctx)) { ret=-1; goto err; }
|
||||
sor = BN_CTX_get(ctx);
|
||||
if (!BN_mod_mul(sor, sig_s, rr, order, ctx)) { ret=-1; goto err; }
|
||||
eor = BN_CTX_get(ctx);
|
||||
if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; }
|
||||
if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; }
|
||||
if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; }
|
||||
|
||||
ret = 1;
|
||||
|
||||
err:
|
||||
if (ctx) {
|
||||
BN_CTX_end(ctx);
|
||||
BN_CTX_free(ctx);
|
||||
}
|
||||
if (R != NULL) EC_POINT_free(R);
|
||||
if (O != NULL) EC_POINT_free(O);
|
||||
if (Q != NULL) EC_POINT_free(Q);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CKey::SetCompressedPubKey()
|
||||
{
|
||||
EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED);
|
||||
fCompressedPubKey = true;
|
||||
}
|
||||
|
||||
void CKey::SetUnCompressedPubKey()
|
||||
{
|
||||
EC_KEY_set_conv_form(pkey, POINT_CONVERSION_UNCOMPRESSED);
|
||||
fCompressedPubKey = false;
|
||||
}
|
||||
|
||||
EC_KEY* CKey::GetECKey()
|
||||
{
|
||||
return pkey;
|
||||
}
|
||||
|
||||
void CKey::Reset()
|
||||
{
|
||||
fCompressedPubKey = false;
|
||||
if (pkey != NULL)
|
||||
EC_KEY_free(pkey);
|
||||
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
if (pkey == NULL)
|
||||
throw key_error("CKey::CKey() : EC_KEY_new_by_curve_name failed");
|
||||
fSet = false;
|
||||
}
|
||||
|
||||
CKey::CKey()
|
||||
{
|
||||
pkey = NULL;
|
||||
Reset();
|
||||
}
|
||||
|
||||
CKey::CKey(const CKey& b)
|
||||
{
|
||||
pkey = EC_KEY_dup(b.pkey);
|
||||
if (pkey == NULL)
|
||||
throw key_error("CKey::CKey(const CKey&) : EC_KEY_dup failed");
|
||||
fSet = b.fSet;
|
||||
}
|
||||
|
||||
CKey& CKey::operator=(const CKey& b)
|
||||
{
|
||||
if (!EC_KEY_copy(pkey, b.pkey))
|
||||
throw key_error("CKey::operator=(const CKey&) : EC_KEY_copy failed");
|
||||
fSet = b.fSet;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
CKey::~CKey()
|
||||
{
|
||||
EC_KEY_free(pkey);
|
||||
}
|
||||
|
||||
bool CKey::IsNull() const
|
||||
{
|
||||
return !fSet;
|
||||
}
|
||||
|
||||
bool CKey::IsCompressed() const
|
||||
{
|
||||
return fCompressedPubKey;
|
||||
}
|
||||
|
||||
int CompareBigEndian(const unsigned char *c1, size_t c1len, const unsigned char *c2, size_t c2len) {
|
||||
while (c1len > c2len) {
|
||||
if (*c1)
|
||||
return 1;
|
||||
c1++;
|
||||
c1len--;
|
||||
}
|
||||
while (c2len > c1len) {
|
||||
if (*c2)
|
||||
return -1;
|
||||
c2++;
|
||||
c2len--;
|
||||
}
|
||||
while (c1len > c2len) { if (*c1) return 1; c1++; c1len--; }
|
||||
while (c2len > c1len) { if (*c2) return -1; c2++; c2len--; }
|
||||
while (c1len > 0) {
|
||||
if (*c1 > *c2)
|
||||
return 1;
|
||||
if (*c2 > *c1)
|
||||
return -1;
|
||||
c1++;
|
||||
c2++;
|
||||
c1len--;
|
||||
if (*c1 > *c2) return 1;
|
||||
if (*c2 > *c1) return -1;
|
||||
c1++; c2++; c1len--;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -228,277 +48,332 @@ const unsigned char vchMaxModHalfOrder[32] = {
|
||||
0xDF,0xE9,0x2F,0x46,0x68,0x1B,0x20,0xA0
|
||||
};
|
||||
|
||||
const unsigned char vchZero[0] = {};
|
||||
const unsigned char vchZero[1] = { 0 };
|
||||
|
||||
bool CKey::CheckSignatureElement(const unsigned char *vch, int len, bool half) {
|
||||
return CompareBigEndian(vch, len, vchZero, 0) > 0 &&
|
||||
CompareBigEndian(vch, len, half ? vchMaxModHalfOrder : vchMaxModOrder, 32) <= 0;
|
||||
} // namespace
|
||||
|
||||
bool CKey::CheckSignatureElement(const unsigned char* vchIn, int len, bool half)
|
||||
{
|
||||
return CompareBigEndian(vchIn, len, vchZero, 0) > 0 &&
|
||||
CompareBigEndian(vchIn, len, half ? vchMaxModHalfOrder : vchMaxModOrder, 32) <= 0;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Lifecycle
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
void CKey::Reset()
|
||||
{
|
||||
OPENSSL_cleanse(vch, sizeof(vch));
|
||||
vchPubKey.clear();
|
||||
fSet = false;
|
||||
fHavePrivKey = false;
|
||||
fCompressedPubKey = false;
|
||||
}
|
||||
|
||||
CKey::CKey()
|
||||
{
|
||||
std::memset(vch, 0, sizeof(vch));
|
||||
vchPubKey.clear();
|
||||
fSet = false;
|
||||
fHavePrivKey = false;
|
||||
fCompressedPubKey = false;
|
||||
}
|
||||
|
||||
CKey::CKey(const CKey& b)
|
||||
{
|
||||
*this = b;
|
||||
}
|
||||
|
||||
CKey& CKey::operator=(const CKey& b)
|
||||
{
|
||||
if (this == &b) return *this;
|
||||
std::memcpy(vch, b.vch, sizeof(vch));
|
||||
vchPubKey = b.vchPubKey;
|
||||
fSet = b.fSet;
|
||||
fHavePrivKey = b.fHavePrivKey;
|
||||
fCompressedPubKey = b.fCompressedPubKey;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CKey::~CKey()
|
||||
{
|
||||
OPENSSL_cleanse(vch, sizeof(vch));
|
||||
}
|
||||
|
||||
bool CKey::IsNull() const { return !fSet; }
|
||||
bool CKey::IsCompressed() const { return fCompressedPubKey; }
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Compression toggle
|
||||
//
|
||||
// In the new model the pubkey is always cached at the current compression. If
|
||||
// we hold the private key we can re-derive trivially; if we only hold a public
|
||||
// key, callers don't toggle compression in practice in this codebase, so we
|
||||
// just flip the flag and rely on the next SetPubKey/SetSecret to refresh the
|
||||
// cache.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
void CKey::SetCompressedPubKey()
|
||||
{
|
||||
if (fCompressedPubKey) return;
|
||||
fCompressedPubKey = true;
|
||||
if (fSet && fHavePrivKey) {
|
||||
std::size_t len = 33;
|
||||
vchPubKey.resize(len);
|
||||
if (!ECDSA_pubkey_from_privkey_secp256k1(&vchPubKey[0], &len, vch, /*fCompressed=*/true)) {
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
vchPubKey.resize(len);
|
||||
}
|
||||
}
|
||||
|
||||
void CKey::SetUnCompressedPubKey()
|
||||
{
|
||||
if (!fCompressedPubKey && fSet) return;
|
||||
fCompressedPubKey = false;
|
||||
if (fSet && fHavePrivKey) {
|
||||
std::size_t len = 65;
|
||||
vchPubKey.resize(len);
|
||||
if (!ECDSA_pubkey_from_privkey_secp256k1(&vchPubKey[0], &len, vch, /*fCompressed=*/false)) {
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
vchPubKey.resize(len);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Key generation / load / store
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
void CKey::MakeNewKey(bool fCompressed)
|
||||
{
|
||||
if (!EC_KEY_generate_key(pkey))
|
||||
throw key_error("CKey::MakeNewKey() : EC_KEY_generate_key failed");
|
||||
if (fCompressed)
|
||||
SetCompressedPubKey();
|
||||
fSet = true;
|
||||
// Sample 32 bytes of entropy and reject any that fall outside (0, n).
|
||||
// Probability of needing a retry is ~2^-128.
|
||||
do {
|
||||
if (RAND_bytes(vch, sizeof(vch)) != 1)
|
||||
throw key_error("CKey::MakeNewKey() : RAND_bytes failed");
|
||||
} while (!ECDSA_seckey_verify_secp256k1(vch));
|
||||
|
||||
fSet = true;
|
||||
fHavePrivKey = true;
|
||||
fCompressedPubKey = fCompressed;
|
||||
|
||||
std::size_t len = fCompressed ? 33 : 65;
|
||||
vchPubKey.resize(len);
|
||||
if (!ECDSA_pubkey_from_privkey_secp256k1(&vchPubKey[0], &len, vch, fCompressed)) {
|
||||
Reset();
|
||||
throw key_error("CKey::MakeNewKey() : failed to derive public key");
|
||||
}
|
||||
vchPubKey.resize(len);
|
||||
}
|
||||
|
||||
bool CKey::SetPrivKey(const CPrivKey& vchPrivKey)
|
||||
{
|
||||
const unsigned char* pbegin = &vchPrivKey[0];
|
||||
if (d2i_ECPrivateKey(&pkey, &pbegin, vchPrivKey.size()))
|
||||
{
|
||||
// In testing, d2i_ECPrivateKey can return true
|
||||
// but fill in pkey with a key that fails
|
||||
// EC_KEY_check_key, so:
|
||||
if (EC_KEY_check_key(pkey))
|
||||
{
|
||||
fSet = true;
|
||||
return true;
|
||||
}
|
||||
unsigned char raw[32];
|
||||
if (!ECDSA_privkey_import_der_secp256k1(raw, &vchPrivKey[0], vchPrivKey.size())) {
|
||||
OPENSSL_cleanse(raw, sizeof(raw));
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
// If vchPrivKey data is bad d2i_ECPrivateKey() can
|
||||
// leave pkey in a state where calling EC_KEY_free()
|
||||
// crashes. To avoid that, set pkey to NULL and
|
||||
// leak the memory (a leak is better than a crash)
|
||||
pkey = NULL;
|
||||
Reset();
|
||||
return false;
|
||||
|
||||
// Carry the compressed flag out of the DER blob. The two valid sizes
|
||||
// produced by ECDSA_privkey_export_der_secp256k1 are 214 (compressed) and
|
||||
// 279 (uncompressed); foreign DER blobs are best-effort but those two
|
||||
// cover every record this codebase has ever written.
|
||||
bool fCompressed = (vchPrivKey.size() == 214);
|
||||
|
||||
CSecret secret(raw, raw + 32);
|
||||
OPENSSL_cleanse(raw, sizeof(raw));
|
||||
return SetSecret(secret, fCompressed);
|
||||
}
|
||||
|
||||
bool CKey::SetSecret(const CSecret& vchSecret, bool fCompressed)
|
||||
{
|
||||
EC_KEY_free(pkey);
|
||||
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
if (pkey == NULL)
|
||||
throw key_error("CKey::SetSecret() : EC_KEY_new_by_curve_name failed");
|
||||
if (vchSecret.size() != 32)
|
||||
throw key_error("CKey::SetSecret() : secret must be 32 bytes");
|
||||
BIGNUM *bn = BN_bin2bn(&vchSecret[0],32,BN_new());
|
||||
if (bn == NULL)
|
||||
throw key_error("CKey::SetSecret() : BN_bin2bn failed");
|
||||
if (!EC_KEY_regenerate_key(pkey,bn))
|
||||
{
|
||||
BN_clear_free(bn);
|
||||
throw key_error("CKey::SetSecret() : EC_KEY_regenerate_key failed");
|
||||
if (!ECDSA_seckey_verify_secp256k1(&vchSecret[0]))
|
||||
throw key_error("CKey::SetSecret() : secret is not a valid scalar");
|
||||
|
||||
std::memcpy(vch, &vchSecret[0], 32);
|
||||
fSet = true;
|
||||
fHavePrivKey = true;
|
||||
// Preserve sticky-compression behaviour from the OpenSSL implementation:
|
||||
// if either the explicit argument or the previously-set flag is true,
|
||||
// the result is compressed.
|
||||
bool fComp = fCompressed || fCompressedPubKey;
|
||||
fCompressedPubKey = fComp;
|
||||
|
||||
std::size_t len = fComp ? 33 : 65;
|
||||
vchPubKey.resize(len);
|
||||
if (!ECDSA_pubkey_from_privkey_secp256k1(&vchPubKey[0], &len, vch, fComp)) {
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
BN_clear_free(bn);
|
||||
fSet = true;
|
||||
if (fCompressed || fCompressedPubKey)
|
||||
SetCompressedPubKey();
|
||||
vchPubKey.resize(len);
|
||||
return true;
|
||||
}
|
||||
|
||||
CSecret CKey::GetSecret(bool &fCompressed) const
|
||||
CSecret CKey::GetSecret(bool& fCompressed) const
|
||||
{
|
||||
CSecret vchRet;
|
||||
vchRet.resize(32);
|
||||
const BIGNUM *bn = EC_KEY_get0_private_key(pkey);
|
||||
int nBytes = BN_num_bytes(bn);
|
||||
if (bn == NULL)
|
||||
throw key_error("CKey::GetSecret() : EC_KEY_get0_private_key failed");
|
||||
int n=BN_bn2bin(bn,&vchRet[32 - nBytes]);
|
||||
if (n != nBytes)
|
||||
throw key_error("CKey::GetSecret(): BN_bn2bin failed");
|
||||
if (!fSet || !fHavePrivKey)
|
||||
throw key_error("CKey::GetSecret() : key is not set or has no private component");
|
||||
CSecret out(vch, vch + 32);
|
||||
fCompressed = fCompressedPubKey;
|
||||
return vchRet;
|
||||
return out;
|
||||
}
|
||||
|
||||
CPrivKey CKey::GetPrivKey() const
|
||||
{
|
||||
int nSize = i2d_ECPrivateKey(pkey, NULL);
|
||||
if (!nSize)
|
||||
throw key_error("CKey::GetPrivKey() : i2d_ECPrivateKey failed");
|
||||
CPrivKey vchPrivKey(nSize, 0);
|
||||
unsigned char* pbegin = &vchPrivKey[0];
|
||||
if (i2d_ECPrivateKey(pkey, &pbegin) != nSize)
|
||||
throw key_error("CKey::GetPrivKey() : i2d_ECPrivateKey returned unexpected size");
|
||||
return vchPrivKey;
|
||||
if (!fSet || !fHavePrivKey)
|
||||
throw key_error("CKey::GetPrivKey() : key is not set or has no private component");
|
||||
|
||||
// Max possible output: 279 bytes (uncompressed).
|
||||
CPrivKey out(279, 0);
|
||||
std::size_t out_len = out.size();
|
||||
if (!ECDSA_privkey_export_der_secp256k1(&out[0], &out_len, vch, fCompressedPubKey))
|
||||
throw key_error("CKey::GetPrivKey() : DER export failed");
|
||||
out.resize(out_len);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool CKey::SetPubKey(const CPubKey& vchPubKey)
|
||||
bool CKey::SetPubKey(const CPubKey& cpub)
|
||||
{
|
||||
const unsigned char* pbegin = &vchPubKey.vchPubKey[0];
|
||||
if (o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.vchPubKey.size()))
|
||||
{
|
||||
fSet = true;
|
||||
if (vchPubKey.vchPubKey.size() == 33)
|
||||
SetCompressedPubKey();
|
||||
return true;
|
||||
const std::vector<unsigned char>& vchPub = cpub.vchPubKey;
|
||||
if (vchPub.size() != 33 && vchPub.size() != 65) {
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
pkey = NULL;
|
||||
Reset();
|
||||
return false;
|
||||
if (!ECDSA_pubkey_verify_secp256k1(&vchPub[0], vchPub.size())) {
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
vchPubKey = vchPub;
|
||||
fSet = true;
|
||||
fHavePrivKey = false;
|
||||
fCompressedPubKey = (vchPub.size() == 33);
|
||||
return true;
|
||||
}
|
||||
|
||||
CPubKey CKey::GetPubKey() const
|
||||
{
|
||||
int nSize = i2o_ECPublicKey(pkey, NULL);
|
||||
if (!nSize)
|
||||
throw key_error("CKey::GetPubKey() : i2o_ECPublicKey failed");
|
||||
std::vector<unsigned char> vchPubKey(nSize, 0);
|
||||
unsigned char* pbegin = &vchPubKey[0];
|
||||
if (i2o_ECPublicKey(pkey, &pbegin) != nSize)
|
||||
throw key_error("CKey::GetPubKey() : i2o_ECPublicKey returned unexpected size");
|
||||
return CPubKey(vchPubKey);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Sign / verify / recover (all delegate to crypto_ecdsa wrappers)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
bool CKey::Sign(uint256 hash, std::vector<unsigned char>& vchSig)
|
||||
{
|
||||
vchSig.clear();
|
||||
ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&hash, sizeof(hash), pkey);
|
||||
if (sig == NULL)
|
||||
if (!fSet || !fHavePrivKey) return false;
|
||||
|
||||
// libsecp256k1's max DER output is 72 bytes; allocate that and shrink.
|
||||
vchSig.resize(72);
|
||||
std::size_t sig_len = vchSig.size();
|
||||
if (!ECDSA_sign_secp256k1(&vchSig[0], &sig_len,
|
||||
reinterpret_cast<const unsigned char*>(&hash),
|
||||
vch))
|
||||
{
|
||||
vchSig.clear();
|
||||
return false;
|
||||
BN_CTX *ctx = BN_CTX_new();
|
||||
BN_CTX_start(ctx);
|
||||
const EC_GROUP *group = EC_KEY_get0_group(pkey);
|
||||
BIGNUM *order = BN_CTX_get(ctx);
|
||||
BIGNUM *halforder = BN_CTX_get(ctx);
|
||||
EC_GROUP_get_order(group, order, ctx);
|
||||
BN_rshift1(halforder, order);
|
||||
const BIGNUM *sig_r, *sig_s;
|
||||
ECDSA_SIG_get0(sig, &sig_r, &sig_s);
|
||||
if (BN_cmp(sig_s, halforder) > 0) {
|
||||
// enforce low S values, by negating the value (modulo the order) if above order/2.
|
||||
BIGNUM *new_s = BN_new();
|
||||
BN_sub(new_s, order, sig_s);
|
||||
BIGNUM *dup_r = BN_dup(sig_r);
|
||||
ECDSA_SIG_set0(sig, dup_r, new_s);
|
||||
}
|
||||
BN_CTX_end(ctx);
|
||||
BN_CTX_free(ctx);
|
||||
unsigned int nSize = ECDSA_size(pkey);
|
||||
vchSig.resize(nSize); // Make sure it is big enough
|
||||
unsigned char *pos = &vchSig[0];
|
||||
nSize = i2d_ECDSA_SIG(sig, &pos);
|
||||
ECDSA_SIG_free(sig);
|
||||
vchSig.resize(nSize); // Shrink to fit actual size
|
||||
vchSig.resize(sig_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// create a compact signature (65 bytes), which allows reconstructing the used public key
|
||||
// The format is one header byte, followed by two times 32 bytes for the serialized r and s values.
|
||||
// The header byte: 0x1B = first key with even y, 0x1C = first key with odd y,
|
||||
// 0x1D = second key with even y, 0x1E = second key with odd y
|
||||
// Compact signature (65 bytes): one header byte (encoding recid + compression)
|
||||
// followed by 32-byte r and 32-byte s.
|
||||
bool CKey::SignCompact(uint256 hash, std::vector<unsigned char>& vchSig)
|
||||
{
|
||||
bool fOk = false;
|
||||
ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&hash, sizeof(hash), pkey);
|
||||
if (sig==NULL)
|
||||
return false;
|
||||
vchSig.clear();
|
||||
vchSig.resize(65,0);
|
||||
const BIGNUM *sig_r, *sig_s;
|
||||
ECDSA_SIG_get0(sig, &sig_r, &sig_s);
|
||||
int nBitsR = BN_num_bits(sig_r);
|
||||
int nBitsS = BN_num_bits(sig_s);
|
||||
if (nBitsR <= 256 && nBitsS <= 256)
|
||||
if (!fSet || !fHavePrivKey) return false;
|
||||
|
||||
vchSig.resize(65, 0);
|
||||
if (!ECDSA_sign_compact_secp256k1(&vchSig[0],
|
||||
reinterpret_cast<const unsigned char*>(&hash),
|
||||
vch,
|
||||
fCompressedPubKey))
|
||||
{
|
||||
int nRecId = -1;
|
||||
for (int i=0; i<4; i++)
|
||||
{
|
||||
CKey keyRec;
|
||||
keyRec.fSet = true;
|
||||
if (fCompressedPubKey)
|
||||
keyRec.SetCompressedPubKey();
|
||||
if (ECDSA_SIG_recover_key_GFp(keyRec.pkey, sig, (unsigned char*)&hash, sizeof(hash), i, 1) == 1)
|
||||
if (keyRec.GetPubKey() == this->GetPubKey())
|
||||
{
|
||||
nRecId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nRecId == -1)
|
||||
{
|
||||
ECDSA_SIG_free(sig);
|
||||
throw key_error("CKey::SignCompact() : unable to construct recoverable key");
|
||||
}
|
||||
|
||||
vchSig[0] = nRecId+27+(fCompressedPubKey ? 4 : 0);
|
||||
BN_bn2bin(sig_r,&vchSig[33-(nBitsR+7)/8]);
|
||||
BN_bn2bin(sig_s,&vchSig[65-(nBitsS+7)/8]);
|
||||
fOk = true;
|
||||
vchSig.clear();
|
||||
return false;
|
||||
}
|
||||
ECDSA_SIG_free(sig);
|
||||
return fOk;
|
||||
return true;
|
||||
}
|
||||
|
||||
// reconstruct public key from a compact signature
|
||||
// This is only slightly more CPU intensive than just verifying it.
|
||||
// If this function succeeds, the recovered public key is guaranteed to be valid
|
||||
// (the signature is a valid signature of the given data for that key)
|
||||
bool CKey::SetCompactSignature(uint256 hash, const std::vector<unsigned char>& vchSig)
|
||||
{
|
||||
if (vchSig.size() != 65)
|
||||
return false;
|
||||
if (vchSig.size() != 65) return false;
|
||||
int nV = vchSig[0];
|
||||
if (nV<27 || nV>=35)
|
||||
return false;
|
||||
ECDSA_SIG *sig = ECDSA_SIG_new();
|
||||
BIGNUM *sig_r = BN_bin2bn(&vchSig[1],32,NULL);
|
||||
BIGNUM *sig_s = BN_bin2bn(&vchSig[33],32,NULL);
|
||||
ECDSA_SIG_set0(sig, sig_r, sig_s);
|
||||
if (nV < 27 || nV >= 35) return false;
|
||||
|
||||
EC_KEY_free(pkey);
|
||||
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
if (nV >= 31)
|
||||
{
|
||||
SetCompressedPubKey();
|
||||
nV -= 4;
|
||||
}
|
||||
if (ECDSA_SIG_recover_key_GFp(pkey, sig, (unsigned char*)&hash, sizeof(hash), nV - 27, 0) == 1)
|
||||
{
|
||||
fSet = true;
|
||||
ECDSA_SIG_free(sig);
|
||||
return true;
|
||||
}
|
||||
ECDSA_SIG_free(sig);
|
||||
return false;
|
||||
unsigned char pubkey[65];
|
||||
std::size_t pubkey_len = 0;
|
||||
if (!ECDSA_recover_compact_secp256k1(pubkey, &pubkey_len,
|
||||
reinterpret_cast<const unsigned char*>(&hash),
|
||||
&vchSig[0]))
|
||||
return false;
|
||||
|
||||
std::vector<unsigned char> vchPub(pubkey, pubkey + pubkey_len);
|
||||
return SetPubKey(CPubKey(vchPub));
|
||||
}
|
||||
|
||||
bool CKey::Verify(uint256 hash, const std::vector<unsigned char>& vchSig)
|
||||
{
|
||||
// -1 = error, 0 = bad sig, 1 = good
|
||||
if (ECDSA_verify(0, (unsigned char*)&hash, sizeof(hash), &vchSig[0], vchSig.size(), pkey) != 1)
|
||||
return false;
|
||||
if (vchSig.empty() || !fSet) return false;
|
||||
|
||||
return true;
|
||||
return ECDSA_verify_secp256k1(
|
||||
reinterpret_cast<const unsigned char*>(&hash),
|
||||
&vchSig[0], vchSig.size(),
|
||||
&vchPubKey[0], vchPubKey.size());
|
||||
}
|
||||
|
||||
bool CKey::VerifyCompact(uint256 hash, const std::vector<unsigned char>& vchSig)
|
||||
{
|
||||
CKey key;
|
||||
if (!key.SetCompactSignature(hash, vchSig))
|
||||
return false;
|
||||
if (GetPubKey() != key.GetPubKey())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
if (!key.SetCompactSignature(hash, vchSig)) return false;
|
||||
return GetPubKey() == key.GetPubKey();
|
||||
}
|
||||
|
||||
bool CKey::IsValid()
|
||||
{
|
||||
if (!fSet)
|
||||
return false;
|
||||
if (!fSet) return false;
|
||||
|
||||
if (!EC_KEY_check_key(pkey))
|
||||
return false;
|
||||
if (fHavePrivKey) {
|
||||
if (!ECDSA_seckey_verify_secp256k1(vch)) return false;
|
||||
|
||||
bool fCompr;
|
||||
CSecret secret = GetSecret(fCompr);
|
||||
CKey key2;
|
||||
key2.SetSecret(secret, fCompr);
|
||||
return GetPubKey() == key2.GetPubKey();
|
||||
// Re-derive the pubkey and check it matches the cache. This is the
|
||||
// libsecp256k1 equivalent of OpenSSL's "consistency between priv and
|
||||
// pub" check the original implementation performed.
|
||||
unsigned char rederived[65];
|
||||
std::size_t rederived_len = 0;
|
||||
if (!ECDSA_pubkey_from_privkey_secp256k1(rederived, &rederived_len, vch, fCompressedPubKey))
|
||||
return false;
|
||||
if (rederived_len != vchPubKey.size()) return false;
|
||||
return std::memcmp(rederived, &vchPubKey[0], rederived_len) == 0;
|
||||
}
|
||||
|
||||
return ECDSA_pubkey_verify_secp256k1(&vchPubKey[0], vchPubKey.size());
|
||||
}
|
||||
|
||||
bool ECC_InitSanityCheck() {
|
||||
EC_KEY *pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
if(pkey == NULL)
|
||||
return false;
|
||||
EC_KEY_free(pkey);
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Startup smoke test for the cryptography backend.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// TODO Is there more EC functionality that could be missing?
|
||||
bool ECC_InitSanityCheck()
|
||||
{
|
||||
// Verify that libsecp256k1 can validate a trivially-known good secret
|
||||
// (the scalar 1) and reject zero. If either of these fails, the linked
|
||||
// library is broken and we should refuse to start.
|
||||
static const unsigned char one[32] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
|
||||
};
|
||||
static const unsigned char zero[32] = {0};
|
||||
if (!ECDSA_seckey_verify_secp256k1(one)) return false;
|
||||
if ( ECDSA_seckey_verify_secp256k1(zero)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
#include "uint256.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <openssl/ec.h> // for EC_KEY definition
|
||||
|
||||
// secp160k1
|
||||
// const unsigned int PRIVATE_KEY_SIZE = 192;
|
||||
// const unsigned int PUBLIC_KEY_SIZE = 41;
|
||||
@@ -70,7 +68,7 @@ public:
|
||||
CPubKey() { }
|
||||
CPubKey(const std::vector<unsigned char> &vchPubKeyIn) : vchPubKey(vchPubKeyIn) { }
|
||||
friend bool operator==(const CPubKey &a, const CPubKey &b) { return a.vchPubKey == b.vchPubKey; }
|
||||
friend bool operator!=(const CPubKey &a, const CPubKey &b) { return a.vchPubKey != b.vchPubKey; }
|
||||
friend bool operator!=(const CPubKey &a, const CPubKey &b) = default;
|
||||
friend bool operator<(const CPubKey &a, const CPubKey &b) { return a.vchPubKey < b.vchPubKey; }
|
||||
|
||||
IMPLEMENT_SERIALIZE(
|
||||
@@ -101,24 +99,25 @@ public:
|
||||
|
||||
// secure_allocator is defined in allocators.h
|
||||
// CPrivKey is a serialized private key, with all parameters included (279 bytes)
|
||||
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CPrivKey;
|
||||
// CSecret is a serialization of just the secret parameter (32 bytes)
|
||||
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CSecret;
|
||||
using CPrivKey = std::vector<unsigned char, secure_allocator<unsigned char>>;
|
||||
using CSecret = std::vector<unsigned char, secure_allocator<unsigned char>>;
|
||||
|
||||
/** An encapsulated OpenSSL Elliptic Curve key (public and/or private) */
|
||||
/** An encapsulated secp256k1 elliptic-curve key (public and/or private). */
|
||||
class CKey
|
||||
{
|
||||
protected:
|
||||
EC_KEY* pkey;
|
||||
// 32-byte private scalar. Valid iff fSet && fHavePrivKey.
|
||||
unsigned char vch[32];
|
||||
// Cached serialized public key (33 or 65 bytes). Valid iff fSet.
|
||||
std::vector<unsigned char> vchPubKey;
|
||||
bool fSet;
|
||||
bool fCompressedPubKey;
|
||||
bool fHavePrivKey;
|
||||
|
||||
public:
|
||||
void SetCompressedPubKey();
|
||||
void SetUnCompressedPubKey();
|
||||
|
||||
EC_KEY* GetECKey();
|
||||
|
||||
|
||||
void Reset();
|
||||
|
||||
CKey();
|
||||
|
||||
+14
-18
@@ -50,10 +50,9 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut)
|
||||
{
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
ScriptMap::const_iterator mi = mapScripts.find(hash);
|
||||
if (mi != mapScripts.end())
|
||||
if (auto mi = mapScripts.find(hash); mi != mapScripts.end())
|
||||
{
|
||||
redeemScriptOut = (*mi).second;
|
||||
redeemScriptOut = mi->second;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -94,20 +93,19 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
|
||||
if (!SetCrypted())
|
||||
return false;
|
||||
|
||||
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
|
||||
for (; mi != mapCryptedKeys.end(); ++mi)
|
||||
for (const auto& [pubKeyHash, val] : mapCryptedKeys)
|
||||
{
|
||||
const CPubKey &vchPubKey = (*mi).second.first;
|
||||
const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
|
||||
const CPubKey &vchPubKey = val.first;
|
||||
const std::vector<unsigned char> &vchCryptedSecret = val.second;
|
||||
CSecret vchSecret;
|
||||
if(!DecryptSecret(vMasterKeyIn, vchCryptedSecret, vchPubKey.GetHash(), vchSecret))
|
||||
return false;
|
||||
if (vchSecret.size() != 32)
|
||||
return false;
|
||||
CKey key;
|
||||
key.SetPubKey(vchPubKey);
|
||||
key.SetSecret(vchSecret);
|
||||
if (key.GetPubKey() == vchPubKey)
|
||||
CKey decryptedKey;
|
||||
decryptedKey.SetPubKey(vchPubKey);
|
||||
decryptedKey.SetSecret(vchSecret);
|
||||
if (decryptedKey.GetPubKey() == vchPubKey)
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
@@ -159,11 +157,10 @@ bool CCryptoKeyStore::GetKey(const CKeyID &address, CKey& keyOut) const
|
||||
if (!IsCrypted())
|
||||
return CBasicKeyStore::GetKey(address, keyOut);
|
||||
|
||||
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
|
||||
if (mi != mapCryptedKeys.end())
|
||||
if (auto mi = mapCryptedKeys.find(address); mi != mapCryptedKeys.end())
|
||||
{
|
||||
const CPubKey &vchPubKey = (*mi).second.first;
|
||||
const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
|
||||
const CPubKey &vchPubKey = mi->second.first;
|
||||
const std::vector<unsigned char> &vchCryptedSecret = mi->second.second;
|
||||
CSecret vchSecret;
|
||||
if (!DecryptSecret(vMasterKey, vchCryptedSecret, vchPubKey.GetHash(), vchSecret))
|
||||
return false;
|
||||
@@ -184,10 +181,9 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
|
||||
if (!IsCrypted())
|
||||
return CKeyStore::GetPubKey(address, vchPubKeyOut);
|
||||
|
||||
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
|
||||
if (mi != mapCryptedKeys.end())
|
||||
if (auto mi = mapCryptedKeys.find(address); mi != mapCryptedKeys.end())
|
||||
{
|
||||
vchPubKeyOut = (*mi).second.first;
|
||||
vchPubKeyOut = mi->second.first;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-16
@@ -6,8 +6,8 @@
|
||||
#define TRIANGLES_KEYSTORE_H
|
||||
|
||||
#include "crypter.h"
|
||||
#include "util_signal.h"
|
||||
#include "sync.h"
|
||||
#include <boost/signals2/signal.hpp>
|
||||
|
||||
class CScript;
|
||||
|
||||
@@ -44,8 +44,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::map<CKeyID, std::pair<CSecret, bool> > KeyMap;
|
||||
typedef std::map<CScriptID, CScript > ScriptMap;
|
||||
using KeyMap = std::map<CKeyID, std::pair<CSecret, bool>>;
|
||||
using ScriptMap = std::map<CScriptID, CScript>;
|
||||
|
||||
/** Basic key store, that keeps keys in an address->secret map */
|
||||
class CBasicKeyStore : public CKeyStore
|
||||
@@ -70,11 +70,9 @@ public:
|
||||
setAddress.clear();
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
KeyMap::const_iterator mi = mapKeys.begin();
|
||||
while (mi != mapKeys.end())
|
||||
for (const auto& [key, val] : mapKeys)
|
||||
{
|
||||
setAddress.insert((*mi).first);
|
||||
mi++;
|
||||
setAddress.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,11 +80,10 @@ public:
|
||||
{
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
KeyMap::const_iterator mi = mapKeys.find(address);
|
||||
if (mi != mapKeys.end())
|
||||
if (auto mi = mapKeys.find(address); mi != mapKeys.end())
|
||||
{
|
||||
keyOut.Reset();
|
||||
keyOut.SetSecret((*mi).second.first, (*mi).second.second);
|
||||
keyOut.SetSecret(mi->second.first, mi->second.second);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -97,7 +94,7 @@ public:
|
||||
virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const;
|
||||
};
|
||||
|
||||
typedef std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char> > > CryptedKeyMap;
|
||||
using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
|
||||
|
||||
/** Keystore which keeps the private keys encrypted.
|
||||
* It derives from the basic key store, which is used if no encryption is active.
|
||||
@@ -160,24 +157,23 @@ public:
|
||||
bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
|
||||
void GetKeys(std::set<CKeyID> &setAddress) const
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
if (!IsCrypted())
|
||||
{
|
||||
CBasicKeyStore::GetKeys(setAddress);
|
||||
return;
|
||||
}
|
||||
setAddress.clear();
|
||||
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
|
||||
while (mi != mapCryptedKeys.end())
|
||||
for (const auto& [key, val] : mapCryptedKeys)
|
||||
{
|
||||
setAddress.insert((*mi).first);
|
||||
mi++;
|
||||
setAddress.insert(key);
|
||||
}
|
||||
}
|
||||
|
||||
/* Wallet status (encrypted, locked) changed.
|
||||
* Note: Called without locks held.
|
||||
*/
|
||||
boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged;
|
||||
CSignal<void(CCryptoKeyStore*)> NotifyStatusChanged;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+323
-893
File diff suppressed because it is too large
Load Diff
+86
-114
@@ -15,6 +15,8 @@
|
||||
#include "sigcache.h"
|
||||
|
||||
#include <list>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
class CWallet;
|
||||
class CBlock;
|
||||
@@ -29,29 +31,29 @@ class CRequestTracker;
|
||||
class CNode;
|
||||
class CScriptCheck;
|
||||
|
||||
static const int CUTOFF_POW_BLOCK = 9000;
|
||||
static const int CRAPCHAIN_CUTOFF_BLOCK = 17691; // pre-Pharao (version 4) blockchain until block 17691
|
||||
static const int FORK_HEIGHT_V5 = 17651; // v5 hard fork: decentralization + Tor v3 (next block after last checkpoint)
|
||||
static const int FORK_HEIGHT_V5_4 = 2186941; // v5.4: tighter timestamps, deterministic fork resolution
|
||||
constexpr int CUTOFF_POW_BLOCK = 9000;
|
||||
constexpr int CRAPCHAIN_CUTOFF_BLOCK = 17691; // pre-Pharao (version 4) blockchain until block 17691
|
||||
constexpr int FORK_HEIGHT_V5 = 17651; // v5 hard fork: decentralization + Tor v3 (next block after last checkpoint)
|
||||
constexpr int FORK_HEIGHT_V5_4 = 2186941; // v5.4: tighter timestamps, deterministic fork resolution
|
||||
|
||||
static const unsigned int MAX_BLOCK_SIZE = 1000000;
|
||||
static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
|
||||
static const unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50;
|
||||
static const unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100;
|
||||
static const unsigned int MAX_ORPHAN_BLOCKS = 750;
|
||||
static const unsigned int MAX_ORPHAN_BLOCKS_IBD = 1500;
|
||||
static const unsigned int MAX_REORG_DEPTH = 100; // reject reorgs deeper than this (finality)
|
||||
static const unsigned int MAX_INV_SZ = 50000;
|
||||
static const int64_t MIN_TX_FEE = (1 * CENT) / 100;
|
||||
static const int64_t MIN_RELAY_TX_FEE = (1 * CENT) / 100;
|
||||
static const int64_t MAX_MONEY = 2222222 * COIN;
|
||||
static const int64_t COIN_YEAR_REWARD = 33 * CENT; // 33% per year
|
||||
static const int64_t MAX_TRI_PROOF_OF_STAKE = 0.33 * COIN;
|
||||
static const int MODIFIER_INTERVAL_SWITCH = 1;
|
||||
constexpr unsigned int MAX_BLOCK_SIZE = 1000000;
|
||||
constexpr unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
|
||||
constexpr unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50;
|
||||
constexpr unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100;
|
||||
constexpr unsigned int MAX_ORPHAN_BLOCKS = 750;
|
||||
constexpr unsigned int MAX_ORPHAN_BLOCKS_IBD = 1500;
|
||||
constexpr unsigned int MAX_REORG_DEPTH = 100; // reject reorgs deeper than this (finality)
|
||||
constexpr unsigned int MAX_INV_SZ = 50000;
|
||||
constexpr int64_t MIN_TX_FEE = (1 * CENT) / 100;
|
||||
constexpr int64_t MIN_RELAY_TX_FEE = (1 * CENT) / 100;
|
||||
constexpr int64_t MAX_MONEY = 2222222 * COIN;
|
||||
constexpr int64_t COIN_YEAR_REWARD = 33 * CENT; // 33% per year
|
||||
constexpr int64_t MAX_TRI_PROOF_OF_STAKE = 0.33 * COIN;
|
||||
constexpr int MODIFIER_INTERVAL_SWITCH = 1;
|
||||
|
||||
inline bool MoneyRange(int64_t nValue) { return (nValue >= 0 && nValue <= MAX_MONEY); }
|
||||
// Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp.
|
||||
static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC
|
||||
constexpr unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC
|
||||
|
||||
#ifdef USE_UPNP
|
||||
static const int fHaveUPnP = true;
|
||||
@@ -95,7 +97,7 @@ extern int64_t nTimeBestReceived;
|
||||
extern CCriticalSection cs_setpwalletRegistered;
|
||||
extern std::set<CWallet*> setpwalletRegistered;
|
||||
extern unsigned char pchMessageStart[4];
|
||||
extern std::map<uint256, CBlock*> mapOrphanBlocks;
|
||||
extern std::map<uint256, std::unique_ptr<CBlock>> mapOrphanBlocks;
|
||||
|
||||
// Settings
|
||||
extern int64_t nTransactionFee;
|
||||
@@ -107,15 +109,15 @@ extern unsigned int nDerivationMethodIndex;
|
||||
extern bool fEnforceCanonical;
|
||||
|
||||
// Minimum disk space required - used in CheckDiskSpace()
|
||||
static const uint64_t nMinDiskSpace = 52428800;
|
||||
constexpr uint64_t nMinDiskSpace = 52428800;
|
||||
|
||||
class CReserveKey;
|
||||
class CTxDB;
|
||||
class CTxDBBase;
|
||||
class CTxIndex;
|
||||
|
||||
void RegisterWallet(CWallet* pwalletIn);
|
||||
void UnregisterWallet(CWallet* pwalletIn);
|
||||
void SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false, bool fConnect = true);
|
||||
void SyncWithWallets(const CTransaction& tx, const CBlock* pblock = nullptr, bool fUpdate = false, bool fConnect = true);
|
||||
bool ProcessBlock(CNode* pfrom, CBlock* pblock);
|
||||
bool CheckDiskSpace(uint64_t nAdditionalBytes=0);
|
||||
FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode="rb");
|
||||
@@ -135,7 +137,7 @@ int64_t GetProofOfStakeReward(int64_t nCoinAge, int64_t nFees);
|
||||
unsigned int ComputeMinWork(unsigned int nBase, int64_t nTime);
|
||||
unsigned int ComputeMinStake(unsigned int nBase, int64_t nTime, unsigned int nBlockTime);
|
||||
int GetNumBlocksOfPeers();
|
||||
bool IsInitialBlockDownload();
|
||||
[[nodiscard]] bool IsInitialBlockDownload();
|
||||
std::string GetWarnings(std::string strFor);
|
||||
bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock);
|
||||
uint256 WantedByOrphan(const CBlock* pblockOrphan);
|
||||
@@ -186,10 +188,7 @@ public:
|
||||
a.nTxPos == b.nTxPos);
|
||||
}
|
||||
|
||||
friend bool operator!=(const CDiskTxPos& a, const CDiskTxPos& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
friend bool operator!=(const CDiskTxPos& a, const CDiskTxPos& b) = default;
|
||||
|
||||
|
||||
std::string ToString() const
|
||||
@@ -217,8 +216,8 @@ public:
|
||||
|
||||
CInPoint() { SetNull(); }
|
||||
CInPoint(CTransaction* ptxIn, unsigned int nIn) { ptx = ptxIn; n = nIn; }
|
||||
void SetNull() { ptx = NULL; n = (unsigned int) -1; }
|
||||
bool IsNull() const { return (ptx == NULL && n == (unsigned int) -1); }
|
||||
void SetNull() { ptx = nullptr; n = (unsigned int) -1; }
|
||||
bool IsNull() const { return (ptx == nullptr && n == (unsigned int) -1); }
|
||||
};
|
||||
|
||||
|
||||
@@ -246,10 +245,7 @@ public:
|
||||
return (a.hash == b.hash && a.n == b.n);
|
||||
}
|
||||
|
||||
friend bool operator!=(const COutPoint& a, const COutPoint& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
friend bool operator!=(const COutPoint& a, const COutPoint& b) = default;
|
||||
|
||||
std::string ToString() const
|
||||
{
|
||||
@@ -314,10 +310,7 @@ public:
|
||||
a.nSequence == b.nSequence);
|
||||
}
|
||||
|
||||
friend bool operator!=(const CTxIn& a, const CTxIn& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
friend bool operator!=(const CTxIn& a, const CTxIn& b) = default;
|
||||
|
||||
std::string ToStringShort() const
|
||||
{
|
||||
@@ -407,10 +400,7 @@ public:
|
||||
a.scriptPubKey == b.scriptPubKey);
|
||||
}
|
||||
|
||||
friend bool operator!=(const CTxOut& a, const CTxOut& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
friend bool operator!=(const CTxOut& a, const CTxOut& b) = default;
|
||||
|
||||
std::string ToStringShort() const
|
||||
{
|
||||
@@ -434,11 +424,11 @@ public:
|
||||
|
||||
|
||||
|
||||
enum GetMinFee_mode
|
||||
enum class GetMinFeeMode : int
|
||||
{
|
||||
GMF_BLOCK,
|
||||
GMF_RELAY,
|
||||
GMF_SEND,
|
||||
Block,
|
||||
Relay,
|
||||
Send,
|
||||
};
|
||||
|
||||
/** A single unspent transaction output entry in the UTXO database.
|
||||
@@ -485,7 +475,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::map<COutPoint, CUtxoEntry> MapPrevTx;
|
||||
using MapPrevTx = std::map<COutPoint, CUtxoEntry>;
|
||||
|
||||
/** The basic transaction that is broadcasted on the network and contained in
|
||||
* blocks. A transaction can contain multiple inputs and outputs.
|
||||
@@ -599,7 +589,7 @@ public:
|
||||
/** Check for standard transaction types
|
||||
@return True if all outputs (scriptPubKeys) use only standard transaction forms
|
||||
*/
|
||||
bool IsStandard() const;
|
||||
[[nodiscard]] bool IsStandard() const;
|
||||
|
||||
/** Check for standard transaction types
|
||||
@param[in] mapInputs Map of previous transactions that have outputs we're spending
|
||||
@@ -647,9 +637,9 @@ public:
|
||||
*/
|
||||
int64_t GetValueIn(const MapPrevTx& mapInputs) const;
|
||||
|
||||
int64_t GetMinFee(unsigned int nBlockSize=1, enum GetMinFee_mode mode=GMF_BLOCK, unsigned int nBytes = 0) const;
|
||||
int64_t GetMinFee(unsigned int nBlockSize=1, GetMinFeeMode mode=GetMinFeeMode::Block, unsigned int nBytes = 0) const;
|
||||
|
||||
bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=NULL)
|
||||
bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=nullptr)
|
||||
{
|
||||
CAutoFile filein = CAutoFile(OpenBlockFile(pos.nFile, 0, pfileRet ? "rb+" : "rb"), SER_DISK, CLIENT_VERSION);
|
||||
if (!filein)
|
||||
@@ -685,10 +675,7 @@ public:
|
||||
a.nLockTime == b.nLockTime);
|
||||
}
|
||||
|
||||
friend bool operator!=(const CTransaction& a, const CTransaction& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
friend bool operator!=(const CTransaction& a, const CTransaction& b) = default;
|
||||
|
||||
std::string ToStringShort() const
|
||||
{
|
||||
@@ -708,10 +695,10 @@ public:
|
||||
vin.size(),
|
||||
vout.size(),
|
||||
nLockTime);
|
||||
for (unsigned int i = 0; i < vin.size(); i++)
|
||||
str += " " + vin[i].ToString() + "\n";
|
||||
for (unsigned int i = 0; i < vout.size(); i++)
|
||||
str += " " + vout[i].ToString() + "\n";
|
||||
for (const CTxIn& txin : vin)
|
||||
str += " " + txin.ToString() + "\n";
|
||||
for (const CTxOut& txout : vout)
|
||||
str += " " + txout.ToString() + "\n";
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -721,10 +708,10 @@ public:
|
||||
}
|
||||
|
||||
|
||||
bool ReadFromDisk(CTxDB& txdb, COutPoint prevout, CTxIndex& txindexRet);
|
||||
bool ReadFromDisk(CTxDB& txdb, COutPoint prevout);
|
||||
bool ReadFromDisk(CTxDBBase& txdb, COutPoint prevout, CTxIndex& txindexRet);
|
||||
bool ReadFromDisk(CTxDBBase& txdb, COutPoint prevout);
|
||||
bool ReadFromDisk(COutPoint prevout);
|
||||
bool DisconnectInputs(CTxDB& txdb);
|
||||
bool DisconnectInputs(CTxDBBase& txdb);
|
||||
|
||||
/** Fetch UTXO entries for all inputs from the UTXO database or mempool.
|
||||
|
||||
@@ -736,7 +723,7 @@ public:
|
||||
@param[out] fInvalid returns true if transaction is invalid
|
||||
@return Returns true if all inputs are found
|
||||
*/
|
||||
bool FetchInputs(CTxDB& txdb, const MapPrevTx& mapPendingUtxos,
|
||||
bool FetchInputs(CTxDBBase& txdb, const MapPrevTx& mapPendingUtxos,
|
||||
bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid);
|
||||
|
||||
/** Validate inputs against UTXO entries and verify signatures.
|
||||
@@ -747,13 +734,13 @@ public:
|
||||
@param[in] fMiner true if called from CreateNewBlock
|
||||
@return Returns true if all checks succeed
|
||||
*/
|
||||
bool ConnectInputs(CTxDB& txdb, const MapPrevTx& inputs,
|
||||
[[nodiscard]] bool ConnectInputs(CTxDBBase& txdb, const MapPrevTx& inputs,
|
||||
const CBlockIndex* pindexBlock, bool fBlock, bool fMiner,
|
||||
std::vector<CScriptCheck>* pvChecks = NULL);
|
||||
std::vector<CScriptCheck>* pvChecks = nullptr);
|
||||
bool ClientConnectInputs();
|
||||
bool CheckTransaction() const;
|
||||
bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL);
|
||||
bool GetCoinAge(CTxDB& txdb, uint64_t& nCoinAge) const; // triangles: get transaction coin age
|
||||
[[nodiscard]] bool CheckTransaction() const;
|
||||
[[nodiscard]] bool AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs=true, bool* pfMissingInputs=nullptr);
|
||||
bool GetCoinAge(CTxDBBase& txdb, uint64_t& nCoinAge) const; // triangles: get transaction coin age
|
||||
|
||||
protected:
|
||||
const CTxOut& GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const;
|
||||
@@ -805,7 +792,7 @@ public:
|
||||
)
|
||||
|
||||
|
||||
int SetMerkleBranch(const CBlock* pblock=NULL);
|
||||
int SetMerkleBranch(const CBlock* pblock=nullptr);
|
||||
|
||||
// Return depth of transaction in blockchain:
|
||||
// -1 : not in blockchain, and not in memory pool (conflicted transaction)
|
||||
@@ -815,7 +802,7 @@ public:
|
||||
int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
|
||||
bool IsInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; }
|
||||
int GetBlocksToMaturity() const;
|
||||
bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true);
|
||||
bool AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs=true);
|
||||
bool AcceptToMemoryPool();
|
||||
};
|
||||
|
||||
@@ -869,10 +856,7 @@ public:
|
||||
a.vSpent == b.vSpent);
|
||||
}
|
||||
|
||||
friend bool operator!=(const CTxIndex& a, const CTxIndex& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
friend bool operator!=(const CTxIndex& a, const CTxIndex& b) = default;
|
||||
int GetDepthInMainChain() const;
|
||||
|
||||
};
|
||||
@@ -957,6 +941,7 @@ public:
|
||||
vMerkleTree.clear();
|
||||
nDoS = 0;
|
||||
fCachedHash = false;
|
||||
fMerkleTreeCached = false;
|
||||
}
|
||||
|
||||
bool IsNull() const
|
||||
@@ -966,6 +951,7 @@ public:
|
||||
|
||||
mutable uint256 cachedHash;
|
||||
mutable bool fCachedHash;
|
||||
mutable bool fMerkleTreeCached;
|
||||
|
||||
uint256 GetHash() const
|
||||
{
|
||||
@@ -1007,7 +993,7 @@ public:
|
||||
|
||||
std::pair<COutPoint, unsigned int> GetProofOfStake() const
|
||||
{
|
||||
return IsProofOfStake()? std::make_pair(vtx[1].vin[0].prevout, vtx[1].nTime) : std::make_pair(COutPoint(), (unsigned int)0);
|
||||
return IsProofOfStake()? std::pair{vtx[1].vin[0].prevout, vtx[1].nTime} : std::pair{COutPoint(), (unsigned int)0};
|
||||
}
|
||||
|
||||
// triangles: get max transaction timestamp
|
||||
@@ -1021,6 +1007,9 @@ public:
|
||||
|
||||
uint256 BuildMerkleTree() const
|
||||
{
|
||||
if (fMerkleTreeCached)
|
||||
return (vMerkleTree.empty() ? 0 : vMerkleTree.back());
|
||||
|
||||
vMerkleTree.clear();
|
||||
for (const CTransaction& tx : vtx)
|
||||
vMerkleTree.push_back(tx.GetHash());
|
||||
@@ -1035,6 +1024,7 @@ public:
|
||||
}
|
||||
j += nSize;
|
||||
}
|
||||
fMerkleTreeCached = true;
|
||||
return (vMerkleTree.empty() ? 0 : vMerkleTree.back());
|
||||
}
|
||||
|
||||
@@ -1134,31 +1124,31 @@ public:
|
||||
nTime, nBits, nNonce,
|
||||
vtx.size(),
|
||||
HexStr(vchBlockSig.begin(), vchBlockSig.end()).c_str());
|
||||
for (unsigned int i = 0; i < vtx.size(); i++)
|
||||
for (const CTransaction& tx : vtx)
|
||||
{
|
||||
printf(" ");
|
||||
vtx[i].print();
|
||||
tx.print();
|
||||
}
|
||||
printf(" vMerkleTree: ");
|
||||
for (unsigned int i = 0; i < vMerkleTree.size(); i++)
|
||||
printf("%s ", vMerkleTree[i].ToString().substr(0,10).c_str());
|
||||
for (const uint256& merkle : vMerkleTree)
|
||||
printf("%s ", merkle.ToString().substr(0,10).c_str());
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex);
|
||||
bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck=false);
|
||||
bool DisconnectBlock(CTxDBBase& txdb, CBlockIndex* pindex);
|
||||
[[nodiscard]] bool ConnectBlock(CTxDBBase& txdb, CBlockIndex* pindex, bool fJustCheck=false);
|
||||
bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true);
|
||||
bool SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew);
|
||||
bool SetBestChain(CTxDBBase& txdb, CBlockIndex* pindexNew);
|
||||
bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const uint256& hashProofOfStake);
|
||||
bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true, bool fCheckSig=true) const;
|
||||
bool AcceptBlock();
|
||||
[[nodiscard]] bool AcceptBlock();
|
||||
bool GetCoinAge(uint64_t& nCoinAge) const; // triangles: calculate total coin age spent in block
|
||||
bool SignBlock(CWallet& keystore, int64_t nFees);
|
||||
bool CheckBlockSignature() const;
|
||||
|
||||
private:
|
||||
bool SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew);
|
||||
bool SetBestChainInner(CTxDBBase& txdb, CBlockIndex *pindexNew);
|
||||
};
|
||||
|
||||
|
||||
@@ -1212,9 +1202,9 @@ public:
|
||||
|
||||
CBlockIndex()
|
||||
{
|
||||
phashBlock = NULL;
|
||||
pprev = NULL;
|
||||
pnext = NULL;
|
||||
phashBlock = nullptr;
|
||||
pprev = nullptr;
|
||||
pnext = nullptr;
|
||||
nFile = 0;
|
||||
nBlockPos = 0;
|
||||
nHeight = 0;
|
||||
@@ -1235,32 +1225,16 @@ public:
|
||||
nNonce = 0;
|
||||
}
|
||||
|
||||
CBlockIndex(unsigned int nFileIn, unsigned int nBlockPosIn, CBlock& block)
|
||||
CBlockIndex(unsigned int nFileIn, unsigned int nBlockPosIn, CBlock& block) : CBlockIndex()
|
||||
{
|
||||
phashBlock = NULL;
|
||||
pprev = NULL;
|
||||
pnext = NULL;
|
||||
nFile = nFileIn;
|
||||
nBlockPos = nBlockPosIn;
|
||||
nHeight = 0;
|
||||
nChainTrust = 0;
|
||||
nMint = 0;
|
||||
nMoneySupply = 0;
|
||||
nFlags = 0;
|
||||
nStakeModifier = 0;
|
||||
nStakeModifierChecksum = 0;
|
||||
hashProofOfStake = 0;
|
||||
if (block.IsProofOfStake())
|
||||
{
|
||||
SetProofOfStake();
|
||||
prevoutStake = block.vtx[1].vin[0].prevout;
|
||||
nStakeTime = block.vtx[1].nTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevoutStake.SetNull();
|
||||
nStakeTime = 0;
|
||||
}
|
||||
|
||||
nVersion = block.nVersion;
|
||||
hashMerkleRoot = block.hashMerkleRoot;
|
||||
@@ -1313,9 +1287,9 @@ public:
|
||||
|
||||
int64_t GetMedianTimePast() const
|
||||
{
|
||||
int64_t pmedian[nMedianTimeSpan];
|
||||
int64_t* pbegin = &pmedian[nMedianTimeSpan];
|
||||
int64_t* pend = &pmedian[nMedianTimeSpan];
|
||||
std::array<int64_t, nMedianTimeSpan> pmedian{};
|
||||
auto pbegin = pmedian.end();
|
||||
auto pend = pmedian.end();
|
||||
|
||||
const CBlockIndex* pindex = this;
|
||||
for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev)
|
||||
@@ -1541,10 +1515,7 @@ public:
|
||||
Set((*mi).second);
|
||||
}
|
||||
|
||||
CBlockLocator(const std::vector<uint256>& vHaveIn)
|
||||
{
|
||||
vHave = vHaveIn;
|
||||
}
|
||||
CBlockLocator(std::vector<uint256> vHaveIn) : vHave(std::move(vHaveIn)) {}
|
||||
|
||||
IMPLEMENT_SERIALIZE
|
||||
(
|
||||
@@ -1662,7 +1633,7 @@ public:
|
||||
std::map<uint256, CTransaction> mapTx;
|
||||
std::map<COutPoint, CInPoint> mapNextTx;
|
||||
|
||||
bool accept(CTxDB& txdb, CTransaction &tx,
|
||||
bool accept(CTxDBBase& txdb, CTransaction &tx,
|
||||
bool fCheckInputs, bool* pfMissingInputs);
|
||||
bool addUnchecked(const uint256& hash, CTransaction &tx);
|
||||
bool remove(const CTransaction &tx, bool fRecursive = false);
|
||||
@@ -1678,6 +1649,7 @@ public:
|
||||
|
||||
bool exists(uint256 hash)
|
||||
{
|
||||
LOCK(cs);
|
||||
return (mapTx.count(hash) != 0);
|
||||
}
|
||||
|
||||
@@ -1744,7 +1716,7 @@ public:
|
||||
{
|
||||
if (i <= 1) {
|
||||
// Always prefill coinbase (idx 0) and coinstake (idx 1)
|
||||
vPrefilledTxn.push_back(std::make_pair(i, block.vtx[i]));
|
||||
vPrefilledTxn.push_back({i, block.vtx[i]});
|
||||
} else {
|
||||
vShortTxIds.push_back(GetShortTxId(block.vtx[i].GetHash(), nShortIdNonce));
|
||||
}
|
||||
@@ -1818,7 +1790,7 @@ private:
|
||||
int nHashType;
|
||||
|
||||
public:
|
||||
CScriptCheck() : ptxTo(NULL), nIn(0), nHashType(0) {}
|
||||
CScriptCheck() : ptxTo(nullptr), nIn(0), nHashType(0) {}
|
||||
|
||||
CScriptCheck(const CScript& scriptPubKeyIn, const CScript& scriptSigIn,
|
||||
const CTransaction& txToIn, unsigned int nInIn, int nHashTypeIn)
|
||||
@@ -1845,6 +1817,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
extern CCheckQueue<CScriptCheck>* pScriptCheckQueue;
|
||||
extern std::unique_ptr<CCheckQueue<CScriptCheck>> pScriptCheckQueue;
|
||||
|
||||
#endif
|
||||
|
||||
+11
-11
@@ -50,7 +50,7 @@ uint64_t nLastBlockSize = 0;
|
||||
int64_t nLastCoinStakeSearchInterval = 0;
|
||||
|
||||
// We want to sort transactions by priority and fee, so:
|
||||
typedef std::tuple<double, double, CTransaction*> TxPriority;
|
||||
using TxPriority = std::tuple<double, double, CTransaction*>;
|
||||
class TxPriorityCompare
|
||||
{
|
||||
bool byFee;
|
||||
@@ -79,7 +79,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
// Create new block
|
||||
unique_ptr<CBlock> pblock(new CBlock());
|
||||
if (!pblock.get())
|
||||
return NULL;
|
||||
return nullptr;
|
||||
|
||||
CBlockIndex* pindexPrev = pindexBest;
|
||||
|
||||
@@ -136,7 +136,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
int64_t nFees = 0;
|
||||
{
|
||||
LOCK2(cs_main, mempool.cs);
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
|
||||
// Priority order to process transactions
|
||||
list<COrphan> vOrphan; // list memory doesn't move
|
||||
@@ -145,13 +145,12 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
// This vector will be sorted into a priority queue:
|
||||
vector<TxPriority> vecPriority;
|
||||
vecPriority.reserve(mempool.mapTx.size());
|
||||
for (map<uint256, CTransaction>::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi)
|
||||
for (auto& [hash, tx] : mempool.mapTx)
|
||||
{
|
||||
CTransaction& tx = (*mi).second;
|
||||
if (tx.IsCoinBase() || tx.IsCoinStake() || !tx.IsFinal())
|
||||
continue;
|
||||
|
||||
COrphan* porphan = NULL;
|
||||
COrphan* porphan = nullptr;
|
||||
double dPriority = 0;
|
||||
int64_t nTotalIn = 0;
|
||||
bool fMissingInputs = false;
|
||||
@@ -210,7 +209,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
porphan->dFeePerKb = dFeePerKb;
|
||||
}
|
||||
else
|
||||
vecPriority.push_back(TxPriority(dPriority, dFeePerKb, &(*mi).second));
|
||||
vecPriority.push_back(TxPriority(dPriority, dFeePerKb, &tx));
|
||||
}
|
||||
|
||||
// Collect transactions into block
|
||||
@@ -247,7 +246,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
||||
continue;
|
||||
|
||||
// Transaction fee
|
||||
int64_t nMinFee = tx.GetMinFee(nBlockSize, GMF_BLOCK);
|
||||
int64_t nMinFee = tx.GetMinFee(nBlockSize, GetMinFeeMode::Block);
|
||||
|
||||
// Skip free transactions if we're past the minimum block size:
|
||||
if (fSortedByFee && (dFeePerKb < nMinTxFee) && (nBlockSize + nTxSize >= nBlockMinSize))
|
||||
@@ -372,7 +371,7 @@ bool CheckStake(CBlock* pblock, CWallet& wallet)
|
||||
}
|
||||
|
||||
// Process this block the same as if we had received it from another node
|
||||
if (!ProcessBlock(NULL, pblock))
|
||||
if (!ProcessBlock(nullptr, pblock))
|
||||
return error("CheckStake() : ProcessBlock, block not accepted");
|
||||
}
|
||||
|
||||
@@ -387,6 +386,7 @@ void StakeMiner(CWallet *pwallet)
|
||||
RenameThread("Triangles-miner");
|
||||
|
||||
bool fTryToSync = true;
|
||||
bool fForceStaking = GetBoolArg("-forcestaking", false);
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -401,7 +401,7 @@ void StakeMiner(CWallet *pwallet)
|
||||
return;
|
||||
}
|
||||
|
||||
while (vNodes.empty() || IsInitialBlockDownload())
|
||||
while (!fForceStaking && (vNodes.empty() || IsInitialBlockDownload()))
|
||||
{
|
||||
nLastCoinStakeSearchInterval = 0;
|
||||
fTryToSync = true;
|
||||
@@ -410,7 +410,7 @@ void StakeMiner(CWallet *pwallet)
|
||||
return;
|
||||
}
|
||||
|
||||
if (fTryToSync)
|
||||
if (fTryToSync && !fForceStaking)
|
||||
{
|
||||
fTryToSync = false;
|
||||
if (vNodes.size() < 2 || nBestHeight < GetNumBlocksOfPeers())
|
||||
|
||||
+47
-52
@@ -3,7 +3,6 @@
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "irc.h"
|
||||
#include "db.h"
|
||||
#include "net.h"
|
||||
#include "main.h"
|
||||
@@ -48,7 +47,7 @@ void ThreadMapPort2(void* parg);
|
||||
#endif
|
||||
void ThreadHTTPSeedFetch(void* parg);
|
||||
bool ThreadHTTPSeedFetch2(void* parg);
|
||||
bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false);
|
||||
bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false);
|
||||
|
||||
|
||||
struct LocalServiceInfo {
|
||||
@@ -60,7 +59,7 @@ struct LocalServiceInfo {
|
||||
// Global state variables
|
||||
//
|
||||
bool fClient = false;
|
||||
//bool fDiscover = true;
|
||||
|
||||
|
||||
#ifdef USE_UPNP
|
||||
bool fUseUPnP = GetBoolArg("-upnp", USE_UPNP);
|
||||
@@ -72,10 +71,10 @@ static CCriticalSection cs_mapLocalHost;
|
||||
static map<CNetAddr, LocalServiceInfo> mapLocalHost;
|
||||
static bool vfReachable[NET_MAX] = {};
|
||||
static bool vfLimited[NET_MAX] = {};
|
||||
static CNode* pnodeLocalHost = NULL;
|
||||
static CNode* pnodeLocalHost = nullptr;
|
||||
CAddress addrSeenByPeer(CService("0.0.0.0", 0), nLocalServices);
|
||||
uint64_t nLocalHostNonce = 0;
|
||||
boost::array<int, THREAD_MAX> vnThreadsRunning;
|
||||
std::array<int, THREAD_MAX> vnThreadsRunning;
|
||||
static std::vector<SOCKET> vhListenSocket;
|
||||
CAddrMan addrman;
|
||||
|
||||
@@ -92,7 +91,7 @@ CCriticalSection cs_vOneShots;
|
||||
set<CNetAddr> setservAddNodeAddresses;
|
||||
CCriticalSection cs_setservAddNodeAddresses;
|
||||
|
||||
static CSemaphore *semOutbound = NULL;
|
||||
static CSemaphore *semOutbound = nullptr;
|
||||
|
||||
void AddOneShot(string strDest)
|
||||
{
|
||||
@@ -348,7 +347,7 @@ bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const cha
|
||||
closesocket(hSocket);
|
||||
return false;
|
||||
}
|
||||
if (pszKeyword == NULL)
|
||||
if (pszKeyword == nullptr)
|
||||
break;
|
||||
if (strLine.find(pszKeyword) != string::npos)
|
||||
{
|
||||
@@ -424,7 +423,7 @@ bool GetMyExternalIP(CNetAddr& ipRet)
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
pszKeyword = NULL; // Returns just IP address
|
||||
pszKeyword = nullptr; // Returns just IP address
|
||||
}
|
||||
|
||||
if (GetMyExternalIP2(addrConnect, pszGet, pszKeyword, ipRet))
|
||||
@@ -470,7 +469,7 @@ CNode* FindNode(const CNetAddr& ip)
|
||||
if ((CNetAddr)pnode->addr == ip)
|
||||
return (pnode);
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CNode* FindNode(std::string addrName)
|
||||
@@ -479,7 +478,7 @@ CNode* FindNode(std::string addrName)
|
||||
for (CNode* pnode : vNodes)
|
||||
if (pnode->addrName == addrName)
|
||||
return (pnode);
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CNode* FindNode(const CService& addr)
|
||||
@@ -490,7 +489,7 @@ CNode* FindNode(const CService& addr)
|
||||
if ((CService)pnode->addr == addr)
|
||||
return (pnode);
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
|
||||
@@ -500,12 +499,12 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
|
||||
if (addrStr.find(".onion") == std::string::npos) {
|
||||
if (fDebug)
|
||||
printf("ConnectNode(): REJECTED non-onion address: %s (Tor-native mode)\n", addrStr.c_str());
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (pszDest == NULL) {
|
||||
if (pszDest == nullptr) {
|
||||
if (IsLocal(addrConnect))
|
||||
return NULL;
|
||||
return nullptr;
|
||||
|
||||
// Look for an existing connection
|
||||
CNode* pnode = FindNode((CService)addrConnect);
|
||||
@@ -558,7 +557,7 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -905,13 +904,9 @@ void ThreadSocketHandler2(void* parg)
|
||||
TRY_LOCK(pnode->cs_vRecvMsg, lockRecv);
|
||||
if (lockRecv)
|
||||
{
|
||||
TRY_LOCK(pnode->cs_mapRequests, lockReq);
|
||||
if (lockReq)
|
||||
{
|
||||
TRY_LOCK(pnode->cs_inventory, lockInv);
|
||||
if (lockInv)
|
||||
fDelete = true;
|
||||
}
|
||||
TRY_LOCK(pnode->cs_inventory, lockInv);
|
||||
if (lockInv)
|
||||
fDelete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1047,7 +1042,7 @@ void ThreadSocketHandler2(void* parg)
|
||||
bool fIsSeed = false;
|
||||
static const char *(*strOnionSeedCheck)[1] = fTestNet ? strTestNetOnionSeed : strMainNetOnionSeed;
|
||||
std::string incomingAddr = addr.ToStringIP();
|
||||
for (unsigned int si = 0; strOnionSeedCheck[si][0] != NULL; si++) {
|
||||
for (unsigned int si = 0; strOnionSeedCheck[si][0] != nullptr; si++) {
|
||||
if (incomingAddr.find(strOnionSeedCheck[si][0]) != std::string::npos) {
|
||||
fIsSeed = true;
|
||||
break;
|
||||
@@ -1209,7 +1204,7 @@ void ThreadMapPort(void* parg)
|
||||
PrintException(&e, "ThreadMapPort()");
|
||||
} catch (...) {
|
||||
vnThreadsRunning[THREAD_UPNP]--;
|
||||
PrintException(NULL, "ThreadMapPort()");
|
||||
PrintException(nullptr, "ThreadMapPort()");
|
||||
}
|
||||
printf("ThreadMapPort exited\n");
|
||||
}
|
||||
@@ -1330,7 +1325,7 @@ void MapPort()
|
||||
printf("MapPort()...\n");
|
||||
if (fUseUPnP && vnThreadsRunning[THREAD_UPNP] < 1)
|
||||
{
|
||||
if (!NewThread(ThreadMapPort, NULL))
|
||||
if (!NewThread(ThreadMapPort, nullptr))
|
||||
printf("Error: ThreadMapPort(ThreadMapPort) failed\n");
|
||||
}
|
||||
}
|
||||
@@ -1408,7 +1403,7 @@ void ThreadOnionSeed(void* parg)
|
||||
static const char *(*strOnionSeed)[1] = fTestNet ? strTestNetOnionSeed : strMainNetOnionSeed;
|
||||
int found = 0;
|
||||
|
||||
for (unsigned int seed_idx = 0; strOnionSeed[seed_idx][0] != NULL; seed_idx++) {
|
||||
for (unsigned int seed_idx = 0; strOnionSeed[seed_idx][0] != nullptr; seed_idx++) {
|
||||
CNetAddr parsed;
|
||||
if (!parsed.SetSpecial(strOnionSeed[seed_idx][0]))
|
||||
throw runtime_error("ThreadOnionSeed() : invalid .onion seed");
|
||||
@@ -1446,7 +1441,7 @@ void ThreadOnionSeed(void* parg)
|
||||
MilliSleep(1000);
|
||||
}
|
||||
if (!fShutdown)
|
||||
ok = ThreadHTTPSeedFetch2(NULL);
|
||||
ok = ThreadHTTPSeedFetch2(nullptr);
|
||||
}
|
||||
if (!ok && !fShutdown)
|
||||
printf("ThreadOnionSeed: all HTTPS seed fetch attempts failed\n");
|
||||
@@ -1504,10 +1499,10 @@ void ThreadOnionSeed(void* parg)
|
||||
else
|
||||
printf("ThreadOnionSeed: low outbound peers (%d), re-seeding...\n", nOutbound);
|
||||
|
||||
ThreadHTTPSeedFetch2(NULL);
|
||||
ThreadHTTPSeedFetch2(nullptr);
|
||||
|
||||
// Re-queue hardcoded seeds for direct connection
|
||||
for (unsigned int seed_idx = 0; strOnionSeed[seed_idx][0] != NULL; seed_idx++) {
|
||||
for (unsigned int seed_idx = 0; strOnionSeed[seed_idx][0] != nullptr; seed_idx++) {
|
||||
std::string oneShotAddr = std::string(strOnionSeed[seed_idx][0])
|
||||
+ ":" + std::to_string(GetDefaultPort());
|
||||
AddOneShot(oneShotAddr);
|
||||
@@ -1592,8 +1587,8 @@ bool ThreadHTTPSeedFetch2(void* parg)
|
||||
|
||||
printf("Fetching seed list from https://%s%s (via Tor)...\n", seedHost.c_str(), seedPath.c_str());
|
||||
|
||||
SSL_CTX* ctx = NULL;
|
||||
SSL* ssl = NULL;
|
||||
SSL_CTX* ctx = nullptr;
|
||||
SSL* ssl = nullptr;
|
||||
SOCKET hSocket = INVALID_SOCKET;
|
||||
|
||||
try {
|
||||
@@ -1616,7 +1611,7 @@ bool ThreadHTTPSeedFetch2(void* parg)
|
||||
|
||||
// Use system default CA certificates for verification
|
||||
SSL_CTX_set_default_verify_paths(ctx);
|
||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
|
||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);
|
||||
|
||||
ssl = SSL_new(ctx);
|
||||
if (!ssl) {
|
||||
@@ -1682,8 +1677,8 @@ bool ThreadHTTPSeedFetch2(void* parg)
|
||||
SSL_free(ssl);
|
||||
SSL_CTX_free(ctx);
|
||||
closesocket(hSocket);
|
||||
ssl = NULL;
|
||||
ctx = NULL;
|
||||
ssl = nullptr;
|
||||
ctx = nullptr;
|
||||
hSocket = INVALID_SOCKET;
|
||||
|
||||
if (response.empty()) {
|
||||
@@ -1790,7 +1785,7 @@ void ThreadHTTPSeedFetch(void* parg)
|
||||
PrintException(&e, "ThreadHTTPSeedFetch()");
|
||||
} catch (...) {
|
||||
vnThreadsRunning[THREAD_HTTPSEED]--;
|
||||
PrintException(NULL, "ThreadHTTPSeedFetch()");
|
||||
PrintException(nullptr, "ThreadHTTPSeedFetch()");
|
||||
}
|
||||
printf("ThreadHTTPSeedFetch exited\n");
|
||||
}
|
||||
@@ -1811,7 +1806,7 @@ void ThreadOpenConnections(void* parg)
|
||||
PrintException(&e, "ThreadOpenConnections()");
|
||||
} catch (...) {
|
||||
vnThreadsRunning[THREAD_OPENCONNECTIONS]--;
|
||||
PrintException(NULL, "ThreadOpenConnections()");
|
||||
PrintException(nullptr, "ThreadOpenConnections()");
|
||||
}
|
||||
printf("ThreadOpenConnections exited\n");
|
||||
}
|
||||
@@ -1885,7 +1880,7 @@ void ThreadOpenConnections2(void* parg)
|
||||
for (string strAddr : mapMultiArgs["-connect"])
|
||||
{
|
||||
CAddress addr;
|
||||
OpenNetworkConnection(addr, NULL, strAddr.c_str());
|
||||
OpenNetworkConnection(addr, nullptr, strAddr.c_str());
|
||||
for (int i = 0; i < 10 && i < nLoop; i++)
|
||||
{
|
||||
MilliSleep(500);
|
||||
@@ -1993,7 +1988,7 @@ void ThreadOpenAddedConnections(void* parg)
|
||||
PrintException(&e, "ThreadOpenAddedConnections()");
|
||||
} catch (...) {
|
||||
vnThreadsRunning[THREAD_ADDEDCONNECTIONS]--;
|
||||
PrintException(NULL, "ThreadOpenAddedConnections()");
|
||||
PrintException(nullptr, "ThreadOpenAddedConnections()");
|
||||
}
|
||||
printf("ThreadOpenAddedConnections exited\n");
|
||||
}
|
||||
@@ -2125,7 +2120,7 @@ void ThreadMessageHandler(void* parg)
|
||||
PrintException(&e, "ThreadMessageHandler()");
|
||||
} catch (...) {
|
||||
vnThreadsRunning[THREAD_MESSAGEHANDLER]--;
|
||||
PrintException(NULL, "ThreadMessageHandler()");
|
||||
PrintException(nullptr, "ThreadMessageHandler()");
|
||||
}
|
||||
printf("ThreadMessageHandler exited\n");
|
||||
}
|
||||
@@ -2146,7 +2141,7 @@ void ThreadMessageHandler2(void* parg)
|
||||
}
|
||||
|
||||
// Poll the connected nodes for messages
|
||||
CNode* pnodeTrickle = NULL;
|
||||
CNode* pnodeTrickle = nullptr;
|
||||
if (!vNodesCopy.empty())
|
||||
pnodeTrickle = vNodesCopy[GetRand(vNodesCopy.size())];
|
||||
for (CNode* pnode : vNodesCopy)
|
||||
@@ -2391,7 +2386,7 @@ void StartNode(void* parg)
|
||||
// Make this thread recognisable as the startup thread
|
||||
RenameThread("Triangles-start");
|
||||
|
||||
if (semOutbound == NULL) {
|
||||
if (semOutbound == nullptr) {
|
||||
// initialize semaphore — use -maxoutbound if specified, else default
|
||||
int nMaxOutbound = (int)GetArg("-maxoutbound", MAX_OUTBOUND_CONNECTIONS);
|
||||
nMaxOutbound = min(nMaxOutbound, (int)GetArg("-maxconnections", 125));
|
||||
@@ -2400,7 +2395,7 @@ void StartNode(void* parg)
|
||||
semOutbound = new CSemaphore(nMaxOutbound);
|
||||
}
|
||||
|
||||
if (pnodeLocalHost == NULL)
|
||||
if (pnodeLocalHost == nullptr)
|
||||
pnodeLocalHost = new CNode(INVALID_SOCKET, CAddress(CService("127.0.0.1", 0), nLocalServices));
|
||||
|
||||
printf("StartNode(): pnodeLocalHost addr: %s\n",
|
||||
@@ -2416,7 +2411,7 @@ void StartNode(void* parg)
|
||||
if (!GetBoolArg("-onionseed", true))
|
||||
printf(".onion seeding disabled\n");
|
||||
else
|
||||
if (!NewThread(ThreadOnionSeed, NULL))
|
||||
if (!NewThread(ThreadOnionSeed, nullptr))
|
||||
printf("Error: NewThread(ThreadOnionSeed) failed\n");
|
||||
|
||||
// Map ports with UPnP (default)
|
||||
@@ -2429,34 +2424,34 @@ void StartNode(void* parg)
|
||||
printf("HTTP seed fetch handled by onion seed thread\n");
|
||||
else if (GetBoolArg("-noseedurl", false))
|
||||
printf("HTTP seed fetch disabled\n");
|
||||
else if (!NewThread(ThreadHTTPSeedFetch, NULL))
|
||||
else if (!NewThread(ThreadHTTPSeedFetch, nullptr))
|
||||
printf("Error: NewThread(ThreadHTTPSeedFetch) failed\n");
|
||||
|
||||
// Send and receive from sockets, accept connections
|
||||
if (!NewThread(ThreadSocketHandler, NULL))
|
||||
if (!NewThread(ThreadSocketHandler, nullptr))
|
||||
printf("Error: NewThread(ThreadSocketHandler) failed\n");
|
||||
|
||||
// Initiate outbound connections from -addnode
|
||||
if (!NewThread(ThreadOpenAddedConnections, NULL))
|
||||
if (!NewThread(ThreadOpenAddedConnections, nullptr))
|
||||
printf("Error: NewThread(ThreadOpenAddedConnections) failed\n");
|
||||
|
||||
// Initiate outbound connections
|
||||
if (!NewThread(ThreadOpenConnections, NULL))
|
||||
if (!NewThread(ThreadOpenConnections, nullptr))
|
||||
printf("Error: NewThread(ThreadOpenConnections) failed\n");
|
||||
|
||||
// Process messages
|
||||
if (!NewThread(ThreadMessageHandler, NULL))
|
||||
if (!NewThread(ThreadMessageHandler, nullptr))
|
||||
printf("Error: NewThread(ThreadMessageHandler) failed\n");
|
||||
|
||||
// Dump network addresses
|
||||
if (!NewThread(ThreadDumpAddress, NULL))
|
||||
if (!NewThread(ThreadDumpAddress, nullptr))
|
||||
printf("Error; NewThread(ThreadDumpAddress) failed\n");
|
||||
|
||||
// Mine proof-of-stake blocks in the background
|
||||
if (!GetBoolArg("-stake", true))
|
||||
printf("Staking disabled at startup (stake=0).\n");
|
||||
else
|
||||
if (!NewThread(ThreadStakeMiner, pwalletMain))
|
||||
if (!NewThread(ThreadStakeMiner, pwalletMain.get()))
|
||||
printf("Error: NewThread(ThreadStakeMiner) failed\n");
|
||||
}
|
||||
|
||||
@@ -2572,8 +2567,8 @@ void RelayTransaction(const CTransaction& tx, const uint256& hash, const CDataSt
|
||||
}
|
||||
|
||||
// Save original serialized message so newer versions are preserved
|
||||
mapRelay.insert(std::make_pair(inv, ss));
|
||||
vRelayExpiration.push_back(std::make_pair(GetTime() + 15 * 60, inv));
|
||||
mapRelay.insert({inv, ss});
|
||||
vRelayExpiration.push_back({GetTime() + 15 * 60, inv});
|
||||
}
|
||||
|
||||
RelayInventory(inv);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#define TRIANGLES_NET_H
|
||||
|
||||
#include <deque>
|
||||
#include <boost/array.hpp>
|
||||
#include <array>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#ifndef WIN32
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "protocol.h"
|
||||
#include "addrman.h"
|
||||
|
||||
class CRequestTracker;
|
||||
class CNode;
|
||||
class CBlockIndex;
|
||||
bool IsInitialBlockDownload();
|
||||
@@ -35,7 +34,7 @@ bool GetMyExternalIP(CNetAddr& ipRet);
|
||||
void AddressCurrentlyConnected(const CService& addr);
|
||||
CNode* FindNode(const CNetAddr& ip);
|
||||
CNode* FindNode(const CService& ip);
|
||||
CNode* ConnectNode(CAddress addrConnect, const char *strDest = NULL);
|
||||
CNode* ConnectNode(CAddress addrConnect, const char *strDest = nullptr);
|
||||
void MapPort();
|
||||
unsigned short GetListenPort();
|
||||
bool BindListenPort(const CService &bindAddr, std::string& strError=REF(std::string()));
|
||||
@@ -64,10 +63,10 @@ bool AddLocal(const CService& addr, int nScore = LOCAL_NONE);
|
||||
bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE);
|
||||
bool SeenLocal(const CService& addr);
|
||||
bool IsLocal(const CService& addr);
|
||||
bool GetLocal(CService &addr, const CNetAddr *paddrPeer = NULL);
|
||||
bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr);
|
||||
bool IsReachable(const CNetAddr &addr);
|
||||
void SetReachable(enum Network net, bool fFlag = true);
|
||||
CAddress GetLocalAddress(const CNetAddr *paddrPeer = NULL);
|
||||
CAddress GetLocalAddress(const CNetAddr *paddrPeer = nullptr);
|
||||
|
||||
|
||||
enum
|
||||
@@ -76,25 +75,6 @@ enum
|
||||
MSG_BLOCK,
|
||||
};
|
||||
|
||||
class CRequestTracker
|
||||
{
|
||||
public:
|
||||
void (*fn)(void*, CDataStream&);
|
||||
void* param1;
|
||||
|
||||
explicit CRequestTracker(void (*fnIn)(void*, CDataStream&)=NULL, void* param1In=NULL)
|
||||
{
|
||||
fn = fnIn;
|
||||
param1 = param1In;
|
||||
}
|
||||
|
||||
bool IsNull()
|
||||
{
|
||||
return fn == NULL;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Thread types */
|
||||
enum threadId
|
||||
{
|
||||
@@ -118,7 +98,7 @@ extern bool fUseUPnP;
|
||||
extern uint64_t nLocalServices;
|
||||
extern uint64_t nLocalHostNonce;
|
||||
extern CAddress addrSeenByPeer;
|
||||
extern boost::array<int, THREAD_MAX> vnThreadsRunning;
|
||||
extern std::array<int, THREAD_MAX> vnThreadsRunning;
|
||||
extern CAddrMan addrman;
|
||||
|
||||
extern std::vector<CNode*> vNodes;
|
||||
@@ -268,8 +248,6 @@ protected:
|
||||
int nMisbehavior;
|
||||
|
||||
public:
|
||||
std::map<uint256, CRequestTracker> mapRequests;
|
||||
CCriticalSection cs_mapRequests;
|
||||
uint256 hashContinue;
|
||||
CBlockIndex* pindexLastGetBlocksBegin;
|
||||
uint256 hashLastGetBlocksEnd;
|
||||
@@ -277,6 +255,7 @@ public:
|
||||
uint256 hashLastGetHeadersEnd;
|
||||
int nStartingHeight;
|
||||
int64_t nLastTipCheck; // last time we asked this peer for chain tip
|
||||
int64_t nLastIbdHeaderRequest; // last time we sent IBD-mode getheaders to this peer (heartbeat throttle)
|
||||
int64_t nAvgBlockLatencyUs; // rolling average block delivery latency (microseconds)
|
||||
int nBlocksDelivered; // count of blocks delivered by this peer
|
||||
int nBestKnownHeight; // highest block height known to this peer (updated from inv/block msgs)
|
||||
@@ -336,6 +315,7 @@ public:
|
||||
hashLastGetHeadersEnd = 0;
|
||||
nStartingHeight = -1;
|
||||
nLastTipCheck = 0;
|
||||
nLastIbdHeaderRequest = 0;
|
||||
nAvgBlockLatencyUs = 0;
|
||||
nBlocksDelivered = 0;
|
||||
nBestKnownHeight = -1;
|
||||
@@ -464,7 +444,7 @@ public:
|
||||
nRequestTime = nNow;
|
||||
else
|
||||
nRequestTime = std::max(nRequestTime + 2 * 60 * 1000000, nNow);
|
||||
mapAskFor.insert(std::make_pair(nRequestTime, inv));
|
||||
mapAskFor.insert({nRequestTime, inv});
|
||||
}
|
||||
|
||||
|
||||
@@ -543,13 +523,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T1>
|
||||
void PushMessage(const char* pszCommand, const T1& a1)
|
||||
template<typename T1, typename... Args>
|
||||
void PushMessage(const char* pszCommand, const T1& a1, const Args&... args)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeginMessage(pszCommand);
|
||||
ssSend << a1;
|
||||
using swallow = int[];
|
||||
(void)swallow{0, ((void)(ssSend << args), 0)...};
|
||||
EndMessage();
|
||||
}
|
||||
catch (...)
|
||||
@@ -559,180 +541,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T1, typename T2>
|
||||
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeginMessage(pszCommand);
|
||||
ssSend << a1 << a2;
|
||||
EndMessage();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
AbortMessage();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T1, typename T2, typename T3>
|
||||
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeginMessage(pszCommand);
|
||||
ssSend << a1 << a2 << a3;
|
||||
EndMessage();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
AbortMessage();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T1, typename T2, typename T3, typename T4>
|
||||
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeginMessage(pszCommand);
|
||||
ssSend << a1 << a2 << a3 << a4;
|
||||
EndMessage();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
AbortMessage();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T1, typename T2, typename T3, typename T4, typename T5>
|
||||
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeginMessage(pszCommand);
|
||||
ssSend << a1 << a2 << a3 << a4 << a5;
|
||||
EndMessage();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
AbortMessage();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
|
||||
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5, const T6& a6)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeginMessage(pszCommand);
|
||||
ssSend << a1 << a2 << a3 << a4 << a5 << a6;
|
||||
EndMessage();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
AbortMessage();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
|
||||
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5, const T6& a6, const T7& a7)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeginMessage(pszCommand);
|
||||
ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7;
|
||||
EndMessage();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
AbortMessage();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
|
||||
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5, const T6& a6, const T7& a7, const T8& a8)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeginMessage(pszCommand);
|
||||
ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8;
|
||||
EndMessage();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
AbortMessage();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9>
|
||||
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5, const T6& a6, const T7& a7, const T8& a8, const T9& a9)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeginMessage(pszCommand);
|
||||
ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9;
|
||||
EndMessage();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
AbortMessage();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PushRequest(const char* pszCommand,
|
||||
void (*fn)(void*, CDataStream&), void* param1)
|
||||
{
|
||||
uint256 hashReply;
|
||||
RAND_bytes((unsigned char*)&hashReply, sizeof(hashReply));
|
||||
|
||||
{
|
||||
LOCK(cs_mapRequests);
|
||||
mapRequests[hashReply] = CRequestTracker(fn, param1);
|
||||
}
|
||||
|
||||
PushMessage(pszCommand, hashReply);
|
||||
}
|
||||
|
||||
template<typename T1>
|
||||
void PushRequest(const char* pszCommand, const T1& a1,
|
||||
void (*fn)(void*, CDataStream&), void* param1)
|
||||
{
|
||||
uint256 hashReply;
|
||||
RAND_bytes((unsigned char*)&hashReply, sizeof(hashReply));
|
||||
|
||||
{
|
||||
LOCK(cs_mapRequests);
|
||||
mapRequests[hashReply] = CRequestTracker(fn, param1);
|
||||
}
|
||||
|
||||
PushMessage(pszCommand, hashReply, a1);
|
||||
}
|
||||
|
||||
template<typename T1, typename T2>
|
||||
void PushRequest(const char* pszCommand, const T1& a1, const T2& a2,
|
||||
void (*fn)(void*, CDataStream&), void* param1)
|
||||
{
|
||||
uint256 hashReply;
|
||||
RAND_bytes((unsigned char*)&hashReply, sizeof(hashReply));
|
||||
|
||||
{
|
||||
LOCK(cs_mapRequests);
|
||||
mapRequests[hashReply] = CRequestTracker(fn, param1);
|
||||
}
|
||||
|
||||
PushMessage(pszCommand, hashReply, a1, a2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PushGetBlocks(CBlockIndex* pindexBegin, uint256 hashEnd);
|
||||
void PushGetHeaders(CBlockIndex* pindexBegin, uint256 hashEnd);
|
||||
|
||||
+11
-12
@@ -13,7 +13,6 @@
|
||||
#endif
|
||||
|
||||
#include "strlcpy.h"
|
||||
#include <boost/algorithm/string/case_conv.hpp> // for to_lower()
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -27,7 +26,7 @@ bool fNameLookup = false;
|
||||
static const unsigned char pchIPv4[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff };
|
||||
|
||||
enum Network ParseNetwork(std::string net) {
|
||||
boost::to_lower(net);
|
||||
net = ToLower(net);
|
||||
if (net == "ipv4") return NET_IPV4;
|
||||
if (net == "ipv6") return NET_IPV6;
|
||||
if (net == "tor") return NET_TOR;
|
||||
@@ -42,7 +41,7 @@ void SplitHostPort(std::string in, int &portOut, std::string &hostOut) {
|
||||
bool fBracketed = fHaveColon && (in[0]=='[' && in[colon-1]==']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe
|
||||
bool fMultiColon = fHaveColon && (in.find_last_of(':',colon-1) != in.npos);
|
||||
if (fHaveColon && (colon==0 || fBracketed || !fMultiColon)) {
|
||||
char *endp = NULL;
|
||||
char *endp = nullptr;
|
||||
int n = strtol(in.c_str() + colon + 1, &endp, 10);
|
||||
if (endp && *endp == 0 && n >= 0) {
|
||||
in = in.substr(0, colon);
|
||||
@@ -88,13 +87,13 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
|
||||
# endif
|
||||
aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST;
|
||||
#endif
|
||||
struct addrinfo *aiRes = NULL;
|
||||
int nErr = getaddrinfo(pszName, NULL, &aiHint, &aiRes);
|
||||
struct addrinfo *aiRes = nullptr;
|
||||
int nErr = getaddrinfo(pszName, nullptr, &aiHint, &aiRes);
|
||||
if (nErr)
|
||||
return false;
|
||||
|
||||
struct addrinfo *aiTrav = aiRes;
|
||||
while (aiTrav != NULL && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions))
|
||||
while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions))
|
||||
{
|
||||
if (aiTrav->ai_family == AF_INET)
|
||||
{
|
||||
@@ -384,7 +383,7 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(hSocket, &fdset);
|
||||
int nRet = select(hSocket + 1, NULL, &fdset, NULL, &timeout);
|
||||
int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout);
|
||||
if (nRet == 0)
|
||||
{
|
||||
printf("connection timeout\n");
|
||||
@@ -454,7 +453,7 @@ bool SetProxy(enum Network net, CService addrProxy, int nSocksVersion) {
|
||||
if (nSocksVersion != 0 && !addrProxy.IsValid())
|
||||
return false;
|
||||
LOCK(cs_proxyInfos);
|
||||
proxyInfo[net] = std::make_pair(addrProxy, nSocksVersion);
|
||||
proxyInfo[net] = {addrProxy, nSocksVersion};
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -473,7 +472,7 @@ bool SetNameProxy(CService addrProxy, int nSocksVersion) {
|
||||
if (nSocksVersion != 0 && !addrProxy.IsValid())
|
||||
return false;
|
||||
LOCK(cs_proxyInfos);
|
||||
nameproxyInfo = std::make_pair(addrProxy, nSocksVersion);
|
||||
nameproxyInfo = {addrProxy, nSocksVersion};
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -868,7 +867,7 @@ std::string CNetAddr::ToStringIP() const
|
||||
unsigned char sha3hash[32];
|
||||
unsigned int sha3len = 0;
|
||||
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
|
||||
EVP_DigestInit_ex(mdctx, EVP_sha3_256(), NULL);
|
||||
EVP_DigestInit_ex(mdctx, EVP_sha3_256(), nullptr);
|
||||
EVP_DigestUpdate(mdctx, checksumInput, 48);
|
||||
EVP_DigestFinal_ex(mdctx, sha3hash, &sha3len);
|
||||
EVP_MD_CTX_free(mdctx);
|
||||
@@ -890,7 +889,7 @@ std::string CNetAddr::ToStringIP() const
|
||||
socklen_t socklen = sizeof(sockaddr);
|
||||
if (serv.GetSockAddr((struct sockaddr*)&sockaddr, &socklen)) {
|
||||
char name[1025] = "";
|
||||
if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name, sizeof(name), NULL, 0, NI_NUMERICHOST))
|
||||
if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name, sizeof(name), nullptr, 0, NI_NUMERICHOST))
|
||||
return std::string(name);
|
||||
}
|
||||
if (IsIPv4())
|
||||
@@ -1043,7 +1042,7 @@ static const int NET_UNKNOWN = NET_MAX + 0;
|
||||
static const int NET_TEREDO = NET_MAX + 1;
|
||||
int static GetExtNetwork(const CNetAddr *addr)
|
||||
{
|
||||
if (addr == NULL)
|
||||
if (addr == nullptr)
|
||||
return NET_UNKNOWN;
|
||||
if (addr->IsRFC4380())
|
||||
return NET_TEREDO;
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/condition_variable.hpp>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
/**
|
||||
* Thread-safe notification queue for SSE (Server-Sent Events) clients.
|
||||
@@ -24,8 +25,8 @@
|
||||
class CNotificationQueue
|
||||
{
|
||||
private:
|
||||
mutable boost::mutex cs;
|
||||
boost::condition_variable cond;
|
||||
mutable std::mutex cs;
|
||||
std::condition_variable cond;
|
||||
|
||||
struct Event {
|
||||
uint64_t id;
|
||||
@@ -43,7 +44,7 @@ public:
|
||||
/** Push a new event. Wakes all waiting SSE clients. */
|
||||
void Push(const std::string& strData)
|
||||
{
|
||||
boost::mutex::scoped_lock lock(cs);
|
||||
std::unique_lock<std::mutex> lock(cs);
|
||||
events.push_back(Event{nNextId++, strData});
|
||||
while (events.size() > MAX_QUEUED_EVENTS)
|
||||
events.pop_front();
|
||||
@@ -59,7 +60,7 @@ public:
|
||||
bool WaitForEvents(uint64_t& nLastId, std::vector<std::string>& vEvents, int nTimeoutMs, const volatile bool& fShutdown)
|
||||
{
|
||||
vEvents.clear();
|
||||
boost::mutex::scoped_lock lock(cs);
|
||||
std::unique_lock<std::mutex> lock(cs);
|
||||
|
||||
// Check for events already in the queue past our read position
|
||||
bool fHasNew = false;
|
||||
@@ -75,7 +76,7 @@ public:
|
||||
if (!fHasNew)
|
||||
{
|
||||
// Wait for new events or timeout
|
||||
cond.timed_wait(lock, boost::posix_time::milliseconds(nTimeoutMs));
|
||||
cond.wait_for(lock, std::chrono::milliseconds(nTimeoutMs));
|
||||
}
|
||||
|
||||
// Drain all events newer than nLastId
|
||||
@@ -97,7 +98,7 @@ public:
|
||||
/** Get the current latest event ID (for clients that want to skip history). */
|
||||
uint64_t GetLatestId() const
|
||||
{
|
||||
boost::mutex::scoped_lock lock(cs);
|
||||
std::unique_lock<std::mutex> lock(cs);
|
||||
return nNextId - 1;
|
||||
}
|
||||
};
|
||||
|
||||
+16
-9
@@ -1,21 +1,28 @@
|
||||
|
||||
#ifndef TRIANGLES_ONIONSEED_H
|
||||
#define TRIANGLES_ONIONSEED_H
|
||||
|
||||
// Hardcoded onion seed nodes for initial peer discovery.
|
||||
// Also fetched dynamically via https://seeds.cryptographic-triangles.org/seeds.txt
|
||||
static const char *strMainNetOnionSeed[][1] = {
|
||||
{"6ygpphp2qsucwvhwefv6h6ehvk6zjf7b7zdp4ggkzjjwe76cg6jwm7id.onion"},
|
||||
{"jbpfhe7zw3qm67wy3j2ayysp3mnrjobopthnko3b3sgahqtecblwqmid.onion"},
|
||||
{"uddaxjbo3lh2zskg7w6gwln4ty5cel7q4c5jbx7fdtv6zf2j47gdlyad.onion"},
|
||||
{"el5sirhhleecuctpeeprelzubpqmoqivvra3rzlwbjttinxa4fq3wnid.onion"},
|
||||
{"sj5dhybnlp3v4y5niyc5unrnd6s43lyx5ibup7rolyosjbi2u2hsbvyd.onion"},
|
||||
{"i3kr5meha7se4ns3wss3h7v46m6uksfzv4wrohdqxpj6n35wyo2bvlid.onion"},
|
||||
{NULL}
|
||||
// DNS2 - primary bootstrap server (194.233.88.206)
|
||||
{"gxvrhv3qitnc6kobrhsrse46bmcfitnybapor3or3oczzuxn6hfzxyid.onion"},
|
||||
// DNS3 - canonical chain reference (74.208.167.19)
|
||||
{"i6tk7soznftvoibtskwlezviskiererhjndpsmrff4kaxw7jnd5izfqd.onion"},
|
||||
// Hetzner Helsinki - ARM64 staking node (46.62.249.20)
|
||||
{"nawqqoazk2hhaglygulpeg6kh7hsgnvi2fursdvpvkantu4ojj26taid.onion"},
|
||||
// Contabo seed 1 (173.212.201.200)
|
||||
{"vmepp7plxngv4qpyngbgtb6njwnmlwy4api64xnwkhaf6fm3qlqtpfad.onion"},
|
||||
// Contabo seed 2
|
||||
{"nsldmfujkiwsfha42ajp5zx7gz3ekwdk4nvowdpf56mayuxnzshuykqd.onion"},
|
||||
// Contabo seed 3
|
||||
{"on4noksywc7b6cdbbxsp535l7j4cugunvlyz3iyhf6sfcg2qzaoy3eqd.onion"},
|
||||
// Contabo seed 4
|
||||
{"3uyzltm5cy7xzunncp3d7ariw75erabdnj4l3cxwvsxb6h4orc7eiqad.onion"},
|
||||
{nullptr}
|
||||
};
|
||||
|
||||
static const char *strTestNetOnionSeed[][1] = {
|
||||
{NULL}
|
||||
{nullptr}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+2
-1
@@ -68,7 +68,8 @@ class CMessageHeader
|
||||
/** nServices flags */
|
||||
enum
|
||||
{
|
||||
NODE_NETWORK = (1 << 0),
|
||||
NODE_NETWORK = (1 << 0),
|
||||
NODE_SNAPSHOT = (1 << 1), // peer can serve UTXO snapshot chunks
|
||||
};
|
||||
|
||||
/** A CService with information about it as peer */
|
||||
|
||||
+5
-37
@@ -4,7 +4,6 @@
|
||||
#include "addresstablemodel.h"
|
||||
#include "transactiontablemodel.h"
|
||||
|
||||
#include "alert.h"
|
||||
#include "main.h"
|
||||
#include "ui_interface.h"
|
||||
|
||||
@@ -94,25 +93,6 @@ void ClientModel::updateNumConnections(int numConnections)
|
||||
emit numConnectionsChanged(numConnections);
|
||||
}
|
||||
|
||||
void ClientModel::updateAlert(const QString &hash, int status)
|
||||
{
|
||||
// Show error message notification for new alert
|
||||
if(status == CT_NEW)
|
||||
{
|
||||
uint256 hash_256;
|
||||
hash_256.SetHex(hash.toStdString());
|
||||
CAlert alert = CAlert::getAlertByHash(hash_256);
|
||||
if(!alert.IsNull())
|
||||
{
|
||||
emit error(tr("Network Alert"), QString::fromStdString(alert.strStatusBar), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Emit a numBlocksChanged when the status message changes,
|
||||
// so that the view recomputes and updates the status bar.
|
||||
emit numBlocksChanged(getNumBlocks(), getNumBlocksOfPeers());
|
||||
}
|
||||
|
||||
double ClientModel::GetDifficulty() const
|
||||
{
|
||||
// Floating point number that is a multiple of the minimum difficulty,
|
||||
@@ -200,27 +180,15 @@ static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConn
|
||||
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())),
|
||||
Q_ARG(int, status));
|
||||
}
|
||||
|
||||
void ClientModel::subscribeToCoreSignals()
|
||||
{
|
||||
// Connect signals to client
|
||||
uiInterface.NotifyBlocksChanged.connect(boost::bind(NotifyBlocksChanged, this));
|
||||
uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1));
|
||||
uiInterface.NotifyAlertChanged.connect(boost::bind(NotifyAlertChanged, this, _1, _2));
|
||||
m_core_signal_connections.add(uiInterface.NotifyBlocksChanged.connect(
|
||||
[this]() { NotifyBlocksChanged(this); }));
|
||||
m_core_signal_connections.add(uiInterface.NotifyNumConnectionsChanged.connect(
|
||||
[this](int n) { NotifyNumConnectionsChanged(this, n); }));
|
||||
}
|
||||
|
||||
void ClientModel::unsubscribeFromCoreSignals()
|
||||
{
|
||||
// Disconnect signals from client
|
||||
uiInterface.NotifyBlocksChanged.disconnect(boost::bind(NotifyBlocksChanged, this));
|
||||
uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1));
|
||||
uiInterface.NotifyAlertChanged.disconnect(boost::bind(NotifyAlertChanged, this, _1, _2));
|
||||
m_core_signal_connections.disconnect_all();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "../util_signal.h"
|
||||
|
||||
class OptionsModel;
|
||||
class AddressTableModel;
|
||||
class TransactionTableModel;
|
||||
@@ -57,6 +59,8 @@ private:
|
||||
|
||||
void subscribeToCoreSignals();
|
||||
void unsubscribeFromCoreSignals();
|
||||
|
||||
CSignalConnections m_core_signal_connections;
|
||||
signals:
|
||||
void numConnectionsChanged(int count);
|
||||
void numBlocksChanged(int count, int countOfPeers);
|
||||
@@ -67,7 +71,6 @@ signals:
|
||||
public slots:
|
||||
void updateTimer();
|
||||
void updateNumConnections(int numConnections);
|
||||
void updateAlert(const QString &hash, int status);
|
||||
};
|
||||
|
||||
#endif // CLIENTMODEL_H
|
||||
|
||||
@@ -545,7 +545,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
|
||||
int64_t nFee = nTransactionFee * (1 + (int64_t)nBytes / 1000);
|
||||
|
||||
// Min Fee
|
||||
int64_t nMinFee = txDummy.GetMinFee(1, GMF_SEND, nBytes);
|
||||
int64_t nMinFee = txDummy.GetMinFee(1, GetMinFeeMode::Send, nBytes);
|
||||
|
||||
nPayFee = max(nFee, nMinFee);
|
||||
|
||||
|
||||
+22
-22
@@ -20,8 +20,8 @@
|
||||
#include <QDesktopServices>
|
||||
#include <QThread>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#ifdef WIN32
|
||||
#ifdef _WIN32_WINNT
|
||||
@@ -240,10 +240,10 @@ bool isObscured(QWidget *w)
|
||||
|
||||
void openDebugLogfile()
|
||||
{
|
||||
boost::filesystem::path pathDebug = GetDataDir() / "debug.log";
|
||||
std::filesystem::path pathDebug = GetDataDir() / "debug.log";
|
||||
|
||||
/* Open debug.log with the associated application */
|
||||
if (boost::filesystem::exists(pathDebug))
|
||||
if (std::filesystem::exists(pathDebug))
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(pathDebug.string())));
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt)
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
boost::filesystem::path static StartupShortcutPath()
|
||||
std::filesystem::path static StartupShortcutPath()
|
||||
{
|
||||
return GetSpecialFolderPath(CSIDL_STARTUP) / "triangles.lnk";
|
||||
}
|
||||
@@ -280,13 +280,13 @@ boost::filesystem::path static StartupShortcutPath()
|
||||
bool GetStartOnSystemStartup()
|
||||
{
|
||||
// check for triangles.lnk
|
||||
return boost::filesystem::exists(StartupShortcutPath());
|
||||
return std::filesystem::exists(StartupShortcutPath());
|
||||
}
|
||||
|
||||
bool SetStartOnSystemStartup(bool fAutoStart)
|
||||
{
|
||||
// If the shortcut exists already, remove it for updating
|
||||
boost::filesystem::remove(StartupShortcutPath());
|
||||
std::filesystem::remove(StartupShortcutPath());
|
||||
|
||||
if (fAutoStart)
|
||||
{
|
||||
@@ -343,9 +343,9 @@ bool SetStartOnSystemStartup(bool fAutoStart)
|
||||
// Follow the Desktop Application Autostart Spec:
|
||||
// http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html
|
||||
|
||||
boost::filesystem::path static GetAutostartDir()
|
||||
std::filesystem::path static GetAutostartDir()
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
char* pszConfigHome = getenv("XDG_CONFIG_HOME");
|
||||
if (pszConfigHome) return fs::path(pszConfigHome) / "autostart";
|
||||
@@ -354,14 +354,14 @@ boost::filesystem::path static GetAutostartDir()
|
||||
return fs::path();
|
||||
}
|
||||
|
||||
boost::filesystem::path static GetAutostartFilePath()
|
||||
std::filesystem::path static GetAutostartFilePath()
|
||||
{
|
||||
return GetAutostartDir() / "triangles.desktop";
|
||||
}
|
||||
|
||||
bool GetStartOnSystemStartup()
|
||||
{
|
||||
boost::filesystem::ifstream optionFile(GetAutostartFilePath());
|
||||
std::ifstream optionFile(GetAutostartFilePath());
|
||||
if (!optionFile.good())
|
||||
return false;
|
||||
// Scan through file for "Hidden=true":
|
||||
@@ -381,7 +381,7 @@ bool GetStartOnSystemStartup()
|
||||
bool SetStartOnSystemStartup(bool fAutoStart)
|
||||
{
|
||||
if (!fAutoStart)
|
||||
boost::filesystem::remove(GetAutostartFilePath());
|
||||
std::filesystem::remove(GetAutostartFilePath());
|
||||
else
|
||||
{
|
||||
char pszExePath[MAX_PATH+1];
|
||||
@@ -389,9 +389,9 @@ bool SetStartOnSystemStartup(bool fAutoStart)
|
||||
if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath)-1) == -1)
|
||||
return false;
|
||||
|
||||
boost::filesystem::create_directories(GetAutostartDir());
|
||||
std::filesystem::create_directories(GetAutostartDir());
|
||||
|
||||
boost::filesystem::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc);
|
||||
std::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc);
|
||||
if (!optionFile.good())
|
||||
return false;
|
||||
// Write a triangles.desktop file to the autostart directory:
|
||||
@@ -407,15 +407,15 @@ bool SetStartOnSystemStartup(bool fAutoStart)
|
||||
}
|
||||
#elif defined(Q_OS_MAC) || defined(MAC_OSX) || defined(__APPLE__)
|
||||
|
||||
boost::filesystem::path static GetLaunchAgentsDir()
|
||||
std::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";
|
||||
return std::filesystem::path();
|
||||
return std::filesystem::path(homeDir.toStdString()) / "Library" / "LaunchAgents";
|
||||
}
|
||||
|
||||
boost::filesystem::path static GetAutostartFilePath()
|
||||
std::filesystem::path static GetAutostartFilePath()
|
||||
{
|
||||
return GetLaunchAgentsDir() / "org.triangles.triangles-qt.plist";
|
||||
}
|
||||
@@ -441,7 +441,7 @@ static std::string PlistEscape(const std::string& value)
|
||||
|
||||
bool GetStartOnSystemStartup()
|
||||
{
|
||||
boost::filesystem::ifstream optionFile(GetAutostartFilePath());
|
||||
std::ifstream optionFile(GetAutostartFilePath());
|
||||
if (!optionFile.good())
|
||||
return false;
|
||||
|
||||
@@ -459,16 +459,16 @@ bool GetStartOnSystemStartup()
|
||||
bool SetStartOnSystemStartup(bool fAutoStart)
|
||||
{
|
||||
if (!fAutoStart)
|
||||
return !boost::filesystem::exists(GetAutostartFilePath()) || boost::filesystem::remove(GetAutostartFilePath());
|
||||
return !std::filesystem::exists(GetAutostartFilePath()) || std::filesystem::remove(GetAutostartFilePath());
|
||||
|
||||
const QString exePath = QApplication::applicationFilePath();
|
||||
if (exePath.isEmpty())
|
||||
return false;
|
||||
|
||||
const QString workingDir = QFileInfo(exePath).absolutePath();
|
||||
boost::filesystem::create_directories(GetLaunchAgentsDir());
|
||||
std::filesystem::create_directories(GetLaunchAgentsDir());
|
||||
|
||||
boost::filesystem::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc);
|
||||
std::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc);
|
||||
if (!optionFile.good())
|
||||
return false;
|
||||
|
||||
|
||||
+10
-10
@@ -14,7 +14,7 @@
|
||||
#include <QCheckBox>
|
||||
#include <QApplication>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
#include <set>
|
||||
|
||||
@@ -152,28 +152,28 @@ void IntroDialog::on_defaultRadio_toggled(bool checked)
|
||||
void IntroDialog::updateFreeSpace()
|
||||
{
|
||||
QString path = getDataDirectory();
|
||||
boost::filesystem::path fsPath(path.toStdString());
|
||||
std::filesystem::path fsPath(path.toStdString());
|
||||
|
||||
// Walk up to find an existing parent
|
||||
try {
|
||||
while (!fsPath.empty() && !boost::filesystem::exists(fsPath))
|
||||
while (!fsPath.empty() && !std::filesystem::exists(fsPath))
|
||||
fsPath = fsPath.parent_path();
|
||||
|
||||
if (!fsPath.empty()) {
|
||||
boost::filesystem::space_info si = boost::filesystem::space(fsPath);
|
||||
std::filesystem::space_info si = std::filesystem::space(fsPath);
|
||||
double freeGB = (double)si.available / (1024.0 * 1024.0 * 1024.0);
|
||||
freeSpaceLabel->setText(tr("Free space: %1 GB").arg(QString::number(freeGB, 'f', 2)));
|
||||
} else {
|
||||
freeSpaceLabel->setText(tr("Cannot determine free space"));
|
||||
}
|
||||
} catch (const boost::filesystem::filesystem_error &) {
|
||||
} catch (const std::filesystem::filesystem_error &) {
|
||||
freeSpaceLabel->setText(tr("Cannot determine free space"));
|
||||
}
|
||||
}
|
||||
|
||||
bool IntroDialog::pickDataDirectory()
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
QSettings settings;
|
||||
// If -datadir was passed on the command line, skip the dialog entirely
|
||||
@@ -305,10 +305,10 @@ bool IntroDialog::pickDataDirectory()
|
||||
return true;
|
||||
}
|
||||
|
||||
static void copyDirectoryRecursive(const boost::filesystem::path& src,
|
||||
const boost::filesystem::path& dst)
|
||||
static void copyDirectoryRecursive(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
fs::create_directories(dst);
|
||||
for (fs::directory_iterator it(src), end; it != end; ++it) {
|
||||
fs::path dstChild = dst / it->path().filename();
|
||||
@@ -322,7 +322,7 @@ static void copyDirectoryRecursive(const boost::filesystem::path& src,
|
||||
|
||||
bool IntroDialog::migrateDataDirectory(const QString& oldPath, const QString& newPath)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fs::path srcDir(oldPath.toStdString());
|
||||
fs::path dstDir(newPath.toStdString());
|
||||
|
||||
+11
-12
@@ -93,7 +93,7 @@ public:
|
||||
QDateTime received_datetime;
|
||||
|
||||
std::string sPrefix("im");
|
||||
leveldb::Iterator* it = dbSmsg.pdb->NewIterator(leveldb::ReadOptions());
|
||||
rocksdb::Iterator* it = dbSmsg.pdb->NewIterator(rocksdb::ReadOptions());
|
||||
while (dbSmsg.NextSmesg(it, sPrefix, chKey, smsgStored))
|
||||
{
|
||||
uint32_t nPayload = smsgStored.vchMessage.size() - SMSG_HDR_LEN;
|
||||
@@ -121,7 +121,7 @@ public:
|
||||
delete it;
|
||||
|
||||
sPrefix = "sm";
|
||||
it = dbSmsg.pdb->NewIterator(leveldb::ReadOptions());
|
||||
it = dbSmsg.pdb->NewIterator(rocksdb::ReadOptions());
|
||||
while (dbSmsg.NextSmesg(it, sPrefix, chKey, smsgStored))
|
||||
{
|
||||
uint32_t nPayload = smsgStored.vchMessage.size() - SMSG_HDR_LEN;
|
||||
@@ -620,20 +620,19 @@ void MessageModel::subscribeToCoreSignals()
|
||||
{
|
||||
qRegisterMetaType<SecMsgStored>("SecMsgStored");
|
||||
|
||||
// Connect signals
|
||||
NotifySecMsgInboxChanged.connect(boost::bind(NotifySecMsgInbox, this, _1));
|
||||
NotifySecMsgOutboxChanged.connect(boost::bind(NotifySecMsgOutbox, this, _1));
|
||||
NotifySecMsgWalletUnlocked.connect(boost::bind(NotifySecMsgWallet, this));
|
||||
|
||||
m_core_signal_connections.add(NotifySecMsgInboxChanged.connect(
|
||||
[this](SecMsgStored& hdr) { NotifySecMsgInbox(this, hdr); }));
|
||||
m_core_signal_connections.add(NotifySecMsgOutboxChanged.connect(
|
||||
[this](SecMsgStored& hdr) { NotifySecMsgOutbox(this, hdr); }));
|
||||
m_core_signal_connections.add(NotifySecMsgWalletUnlocked.connect(
|
||||
[this]() { NotifySecMsgWallet(this); }));
|
||||
|
||||
connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
|
||||
}
|
||||
|
||||
void MessageModel::unsubscribeFromCoreSignals()
|
||||
{
|
||||
// Disconnect signals
|
||||
NotifySecMsgInboxChanged.disconnect(boost::bind(NotifySecMsgInbox, this, _1));
|
||||
NotifySecMsgOutboxChanged.disconnect(boost::bind(NotifySecMsgOutbox, this, _1));
|
||||
NotifySecMsgWalletUnlocked.disconnect(boost::bind(NotifySecMsgWallet, this));
|
||||
|
||||
m_core_signal_connections.disconnect_all();
|
||||
|
||||
disconnect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include "allocators.h" /* for SecureString */
|
||||
#include "../util_signal.h"
|
||||
#include "smessage.h"
|
||||
#include <map>
|
||||
#include <QSortFilterProxyModel>
|
||||
@@ -175,6 +176,8 @@ private:
|
||||
void subscribeToCoreSignals();
|
||||
void unsubscribeFromCoreSignals();
|
||||
|
||||
CSignalConnections m_core_signal_connections;
|
||||
|
||||
public slots:
|
||||
|
||||
/* Check for new messages */
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "init.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
@@ -374,7 +374,7 @@ void OptionsDialog::on_dataDirBrowseButton_clicked()
|
||||
|
||||
void OptionsDialog::updateDataDirFreeSpace()
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
QString path = dataDirPath->text();
|
||||
fs::path fsPath(path.toStdString());
|
||||
try {
|
||||
@@ -395,7 +395,7 @@ void OptionsDialog::updateDataDirFreeSpace()
|
||||
|
||||
quint64 OptionsDialog::calculateDirSize(const QString& path)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
quint64 totalSize = 0;
|
||||
try {
|
||||
for (fs::recursive_directory_iterator it(path.toStdString()), end; it != end; ++it) {
|
||||
@@ -411,7 +411,7 @@ bool OptionsDialog::handleDataDirChange()
|
||||
if (m_pendingDataDir.isEmpty() || m_pendingDataDir == m_currentDataDir)
|
||||
return false;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
fs::path destPath(m_pendingDataDir.toStdString());
|
||||
|
||||
// Check destination is writable
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "ui_interface.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/interprocess/ipc/message_queue.hpp>
|
||||
#include <boost/version.hpp>
|
||||
@@ -26,6 +25,9 @@ using namespace boost;
|
||||
using namespace boost::interprocess;
|
||||
using namespace boost::posix_time;
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#if defined MAC_OSX || defined __FreeBSD__
|
||||
// URI handling not implemented on OSX yet
|
||||
|
||||
@@ -42,7 +44,7 @@ static bool ipcScanCmd(int argc, char *argv[], bool fRelay)
|
||||
bool fSent = false;
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (boost::algorithm::istarts_with(argv[i], "Triangles:"))
|
||||
if (std::equal(std::begin("Triangles:"), std::end("Triangles:") - 1, argv[i], [](char a, char b) { return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b)); }))
|
||||
{
|
||||
const char *strURI = argv[i];
|
||||
try {
|
||||
|
||||
@@ -244,7 +244,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
|
||||
strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
|
||||
strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true);
|
||||
|
||||
CTxDB txdb("r"); // To fetch source txouts
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder; // To fetch source txouts
|
||||
|
||||
strHTML += "<br><b>" + tr("Inputs") + ":</b>";
|
||||
strHTML += "<ul>";
|
||||
|
||||
@@ -145,7 +145,7 @@ int main(int argc, char *argv[])
|
||||
return 0;
|
||||
|
||||
// ... then triangles.conf:
|
||||
if (!boost::filesystem::is_directory(GetDataDir(false)))
|
||||
if (!std::filesystem::is_directory(GetDataDir(false)))
|
||||
{
|
||||
// This message can not be translated, as translation is not initialized yet
|
||||
// (which not yet possible because lang=XX can be overridden in triangles.conf in the data directory)
|
||||
@@ -229,7 +229,7 @@ int main(int argc, char *argv[])
|
||||
// calling Shutdown().
|
||||
|
||||
ClientModel clientModel(&optionsModel);
|
||||
WalletModel walletModel(pwalletMain, &optionsModel);
|
||||
WalletModel walletModel(pwalletMain.get(), &optionsModel);
|
||||
|
||||
window.setClientModel(&clientModel);
|
||||
window.setWalletModel(&walletModel);
|
||||
|
||||
@@ -89,7 +89,8 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
extern CWallet* pwalletMain;
|
||||
#include <memory>
|
||||
extern std::unique_ptr<CWallet> pwalletMain;
|
||||
extern int64_t nLastCoinStakeSearchInterval;
|
||||
extern unsigned int nTargetSpacing;
|
||||
double GetPoSKernelPS();
|
||||
@@ -655,7 +656,7 @@ void TrianglesGUI::ensureMessageModel()
|
||||
if(messageModel || !walletModel)
|
||||
return;
|
||||
|
||||
setMessageModel(new MessageModel(pwalletMain, walletModel, this));
|
||||
setMessageModel(new MessageModel(pwalletMain.get(), walletModel, this));
|
||||
}
|
||||
|
||||
void TrianglesGUI::ensureSendCoinsPage()
|
||||
|
||||
+11
-8
@@ -545,18 +545,21 @@ static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet,
|
||||
|
||||
void WalletModel::subscribeToCoreSignals()
|
||||
{
|
||||
// Connect signals to wallet
|
||||
wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1));
|
||||
wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5));
|
||||
wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
|
||||
m_core_signal_connections.add(wallet->NotifyStatusChanged.connect(
|
||||
[this](CCryptoKeyStore* w) { NotifyKeyStoreStatusChanged(this, w); }));
|
||||
m_core_signal_connections.add(wallet->NotifyAddressBookChanged.connect(
|
||||
[this](CWallet* w, const CTxDestination& address, const std::string& label, bool isMine, ChangeType status) {
|
||||
NotifyAddressBookChanged(this, w, address, label, isMine, status);
|
||||
}));
|
||||
m_core_signal_connections.add(wallet->NotifyTransactionChanged.connect(
|
||||
[this](CWallet* w, const uint256& hash, ChangeType status) {
|
||||
NotifyTransactionChanged(this, w, hash, status);
|
||||
}));
|
||||
}
|
||||
|
||||
void WalletModel::unsubscribeFromCoreSignals()
|
||||
{
|
||||
// Disconnect signals from wallet
|
||||
wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1));
|
||||
wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5));
|
||||
wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
|
||||
m_core_signal_connections.disconnect_all();
|
||||
}
|
||||
|
||||
// WalletModel::UnlockContext implementation
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <QMutex>
|
||||
|
||||
#include "allocators.h" /* for SecureString */
|
||||
#include "../util_signal.h"
|
||||
|
||||
class OptionsModel;
|
||||
class AddressTableModel;
|
||||
@@ -92,7 +93,7 @@ public:
|
||||
};
|
||||
|
||||
// Send coins to a list of recipients
|
||||
SendCoinsReturn sendCoins(const QList<SendCoinsRecipient> &recipients, const CCoinControl *coinControl=NULL);
|
||||
SendCoinsReturn sendCoins(const QList<SendCoinsRecipient> &recipients, const CCoinControl *coinControl=nullptr);
|
||||
|
||||
// Wallet encryption
|
||||
bool setWalletEncrypted(bool encrypted, const SecureString &passphrase);
|
||||
@@ -157,6 +158,8 @@ private:
|
||||
void unsubscribeFromCoreSignals();
|
||||
bool checkBalanceChanged();
|
||||
|
||||
CSignalConnections m_core_signal_connections;
|
||||
|
||||
|
||||
public slots:
|
||||
/* Wallet status might have changed */
|
||||
|
||||
+7
-10
@@ -12,8 +12,6 @@
|
||||
#include "wallet.h"
|
||||
#include "init.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace json_spirit;
|
||||
|
||||
@@ -95,7 +93,7 @@ bool CheckRESTRateLimit(const string& strIP)
|
||||
|
||||
string HTTPReplyREST(int nStatus, const string& strMsg, const string& contentType)
|
||||
{
|
||||
string strCorsOrigin = GetArg("-restcorsorigin", "*");
|
||||
string strCorsOrigin = GetArg(std::string_view{"-restcorsorigin"}, std::string_view{"*"});
|
||||
|
||||
const char *cStatus;
|
||||
if (nStatus == 200) cStatus = "OK";
|
||||
@@ -149,12 +147,11 @@ static void ParseRESTPath(const string& strURI, vector<string>& parts, map<strin
|
||||
}
|
||||
|
||||
// Split path into parts
|
||||
boost::split(parts, path, boost::is_any_of("/"));
|
||||
parts = SplitString(path, '/');
|
||||
|
||||
// Parse query parameters
|
||||
if (!queryString.empty()) {
|
||||
vector<string> pairs;
|
||||
boost::split(pairs, queryString, boost::is_any_of("&"));
|
||||
auto pairs = SplitString(queryString, '&');
|
||||
for (size_t i = 0; i < pairs.size(); i++) {
|
||||
size_t eq = pairs[i].find('=');
|
||||
if (eq != string::npos)
|
||||
@@ -175,12 +172,12 @@ bool IsRESTPath(const string& strURI)
|
||||
static bool RESTAuthorized(map<string, string>& mapHeaders)
|
||||
{
|
||||
// Check Bearer token first (if -restapikey is set)
|
||||
string strApiKey = GetArg("-restapikey", "");
|
||||
string strApiKey = GetArg(std::string_view{"-restapikey"}, std::string_view{""});
|
||||
if (!strApiKey.empty()) {
|
||||
string strAuth = mapHeaders.count("authorization") ? mapHeaders["authorization"] : "";
|
||||
if (strAuth.substr(0, 7) == "Bearer ") {
|
||||
string strToken = strAuth.substr(7);
|
||||
boost::trim(strToken);
|
||||
strToken = TrimString(strToken);
|
||||
if (TimingResistantEqual(strToken, strApiKey))
|
||||
return true;
|
||||
}
|
||||
@@ -300,8 +297,8 @@ static bool HandleBlockHeader(const string& param, string& strReply, int& nStatu
|
||||
result.push_back(Pair("height", pblockindex->nHeight));
|
||||
result.push_back(Pair("version", pblockindex->nVersion));
|
||||
result.push_back(Pair("merkleroot", pblockindex->hashMerkleRoot.GetHex()));
|
||||
result.push_back(Pair("time", (boost::int64_t)pblockindex->GetBlockTime()));
|
||||
result.push_back(Pair("nonce", (boost::uint64_t)pblockindex->nNonce));
|
||||
result.push_back(Pair("time", (int64_t)pblockindex->GetBlockTime()));
|
||||
result.push_back(Pair("nonce", (uint64_t)pblockindex->nNonce));
|
||||
result.push_back(Pair("bits", HexBits(pblockindex->nBits)));
|
||||
result.push_back(Pair("difficulty", GetDifficulty(pblockindex)));
|
||||
result.push_back(Pair("flags", strprintf("%s%s",
|
||||
|
||||
+133
-21
@@ -11,6 +11,19 @@
|
||||
#include "base58.h"
|
||||
#include "utxosnapshot.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
// Windows.h (transitively included) defines these as macros, clobbering Checkpoints:: enum values.
|
||||
#ifdef STRICT
|
||||
#undef STRICT
|
||||
#endif
|
||||
#ifdef ADVISORY
|
||||
#undef ADVISORY
|
||||
#endif
|
||||
#ifdef PERMISSIVE
|
||||
#undef PERMISSIVE
|
||||
#endif
|
||||
|
||||
using namespace json_spirit;
|
||||
using namespace std;
|
||||
|
||||
@@ -21,15 +34,15 @@ double GetDifficulty(const CBlockIndex* blockindex)
|
||||
{
|
||||
// Floating point number that is a multiple of the minimum difficulty,
|
||||
// minimum difficulty = 1.0.
|
||||
if (blockindex == NULL)
|
||||
if (blockindex == nullptr)
|
||||
{
|
||||
if (pindexBest == NULL)
|
||||
if (pindexBest == nullptr)
|
||||
return 1.0;
|
||||
else
|
||||
blockindex = GetLastBlockIndex(pindexBest, false);
|
||||
}
|
||||
|
||||
if (blockindex == NULL)
|
||||
if (blockindex == nullptr)
|
||||
return 1.0;
|
||||
|
||||
int nShift = (blockindex->nBits >> 24) & 0xff;
|
||||
@@ -85,7 +98,7 @@ double GetPoSKernelPS()
|
||||
int nStakesHandled = 0, nStakesTime = 0;
|
||||
|
||||
CBlockIndex* pindex = pindexBest;;
|
||||
CBlockIndex* pindexPrevStake = NULL;
|
||||
CBlockIndex* pindexPrevStake = nullptr;
|
||||
|
||||
while (pindex && nStakesHandled < nPoSInterval)
|
||||
{
|
||||
@@ -115,8 +128,8 @@ Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool fPri
|
||||
result.push_back(Pair("version", block.nVersion));
|
||||
result.push_back(Pair("merkleroot", block.hashMerkleRoot.GetHex()));
|
||||
result.push_back(Pair("mint", ValueFromAmount(blockindex->nMint)));
|
||||
result.push_back(Pair("time", (boost::int64_t)block.GetBlockTime()));
|
||||
result.push_back(Pair("nonce", (boost::uint64_t)block.nNonce));
|
||||
result.push_back(Pair("time", (int64_t)block.GetBlockTime()));
|
||||
result.push_back(Pair("nonce", (uint64_t)block.nNonce));
|
||||
result.push_back(Pair("bits", HexBits(block.nBits)));
|
||||
result.push_back(Pair("difficulty", GetDifficulty(blockindex)));
|
||||
result.push_back(Pair("blocktrust", leftTrim(blockindex->GetBlockTrust().GetHex(), '0')));
|
||||
@@ -317,8 +330,8 @@ Value getblockheader(const Array& params, bool fHelp)
|
||||
result.push_back(Pair("version", pblockindex->nVersion));
|
||||
result.push_back(Pair("merkleroot", pblockindex->hashMerkleRoot.GetHex()));
|
||||
result.push_back(Pair("mint", ValueFromAmount(pblockindex->nMint)));
|
||||
result.push_back(Pair("time", (boost::int64_t)pblockindex->GetBlockTime()));
|
||||
result.push_back(Pair("nonce", (boost::uint64_t)pblockindex->nNonce));
|
||||
result.push_back(Pair("time", (int64_t)pblockindex->GetBlockTime()));
|
||||
result.push_back(Pair("nonce", (uint64_t)pblockindex->nNonce));
|
||||
result.push_back(Pair("bits", HexBits(pblockindex->nBits)));
|
||||
result.push_back(Pair("difficulty", GetDifficulty(pblockindex)));
|
||||
result.push_back(Pair("blocktrust", leftTrim(pblockindex->GetBlockTrust().GetHex(), '0')));
|
||||
@@ -382,7 +395,7 @@ static int64_t ComputeActiveChainSupplyFromBlocks(const std::vector<CBlockIndex*
|
||||
nBlocksScanned = 0;
|
||||
nTransactionsScanned = 0;
|
||||
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
int64_t nSupply = 0;
|
||||
|
||||
for (std::vector<CBlockIndex*>::const_iterator pindexIt = chain.begin(); pindexIt != chain.end(); ++pindexIt)
|
||||
@@ -458,7 +471,7 @@ Value recalculatesupply(const Array& params, bool fHelp)
|
||||
if (!pindexBest)
|
||||
throw runtime_error("recalculatesupply: no best block");
|
||||
|
||||
CTxDB txdbRead("r");
|
||||
auto txdbRead_holder = MakeChainDB("r"); CTxDBBase& txdbRead = *txdbRead_holder;
|
||||
int nUtxoCount = 0;
|
||||
int64_t nUtxoSupply = txdbRead.SumUtxoValues(nUtxoCount);
|
||||
|
||||
@@ -473,7 +486,7 @@ Value recalculatesupply(const Array& params, bool fHelp)
|
||||
|
||||
if (fApply)
|
||||
{
|
||||
CTxDB txdbWrite;
|
||||
auto txdbWrite_holder = MakeChainDB(); CTxDBBase& txdbWrite = *txdbWrite_holder;
|
||||
int64_t nRunningSupply = 0;
|
||||
|
||||
for (std::vector<CBlockIndex*>::const_iterator pindexIt = activeChain.begin(); pindexIt != activeChain.end(); ++pindexIt)
|
||||
@@ -545,6 +558,105 @@ Value recalculatesupply(const Array& params, bool fHelp)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Walks every non-coinbase transaction input across [start_height, end_height]
|
||||
// and runs the existing VerifySignature path. Reports counts and the first 100
|
||||
// failures so the caller can spot regressions when the underlying ECDSA
|
||||
// implementation changes (e.g. OpenSSL EC -> libsecp256k1).
|
||||
Value auditsignatures(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() > 2)
|
||||
throw runtime_error(
|
||||
"auditsignatures [start_height] [end_height]\n"
|
||||
"Walk the active chain in [start_height, end_height] (inclusive) and run\n"
|
||||
"VerifySignature on every non-coinbase input. Returns counts plus up to 100\n"
|
||||
"failures.\n"
|
||||
"Defaults: start = max(1, tip-1000), end = tip.\n"
|
||||
"Pre-migration this should always report 0 failures; post-migration any non-zero\n"
|
||||
"result identifies a behavioural regression in the new ECDSA path.");
|
||||
|
||||
LOCK(cs_main);
|
||||
|
||||
if (!pindexBest)
|
||||
throw runtime_error("auditsignatures: no best block");
|
||||
|
||||
int tip = nBestHeight;
|
||||
int start = (params.size() > 0) ? params[0].get_int() : std::max(1, tip - 1000);
|
||||
int end = (params.size() > 1) ? params[1].get_int() : tip;
|
||||
|
||||
if (start < 1) throw runtime_error("auditsignatures: start_height must be >= 1");
|
||||
if (end > tip) throw runtime_error("auditsignatures: end_height exceeds tip");
|
||||
if (start > end) throw runtime_error("auditsignatures: start_height > end_height");
|
||||
|
||||
// Build forward walk by descending from tip.
|
||||
CBlockIndex* pindex = pindexBest;
|
||||
while (pindex && pindex->nHeight > end)
|
||||
pindex = pindex->pprev;
|
||||
|
||||
std::vector<CBlockIndex*> walk;
|
||||
while (pindex && pindex->nHeight >= start) {
|
||||
walk.push_back(pindex);
|
||||
pindex = pindex->pprev;
|
||||
}
|
||||
std::reverse(walk.begin(), walk.end());
|
||||
|
||||
int nBlocksScanned = 0;
|
||||
int64_t nInputsChecked = 0;
|
||||
int64_t nInputsFailed = 0;
|
||||
Array failures;
|
||||
const size_t kMaxFailures = 100;
|
||||
|
||||
auto recordFailure = [&](int height, const uint256& txid, unsigned int vin, const char* reason) {
|
||||
++nInputsFailed;
|
||||
if (failures.size() >= kMaxFailures) return;
|
||||
Object f;
|
||||
f.push_back(Pair("height", height));
|
||||
f.push_back(Pair("txid", txid.GetHex()));
|
||||
f.push_back(Pair("vin", (int)vin));
|
||||
f.push_back(Pair("reason", reason));
|
||||
failures.push_back(f);
|
||||
};
|
||||
|
||||
for (CBlockIndex* pi : walk) {
|
||||
CBlock block;
|
||||
if (!block.ReadFromDisk(pi, true)) {
|
||||
++nBlocksScanned;
|
||||
continue;
|
||||
}
|
||||
for (const CTransaction& tx : block.vtx) {
|
||||
if (tx.IsCoinBase()) continue;
|
||||
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
|
||||
const COutPoint& prev = tx.vin[i].prevout;
|
||||
CTransaction txFrom;
|
||||
uint256 hashBlock;
|
||||
if (!GetTransaction(prev.hash, txFrom, hashBlock)) {
|
||||
recordFailure(pi->nHeight, tx.GetHash(), i, "prevout transaction not found");
|
||||
continue;
|
||||
}
|
||||
if (prev.n >= txFrom.vout.size()) {
|
||||
recordFailure(pi->nHeight, tx.GetHash(), i, "prevout index out of range");
|
||||
continue;
|
||||
}
|
||||
++nInputsChecked;
|
||||
if (!VerifySignature(txFrom, tx, i, 0))
|
||||
recordFailure(pi->nHeight, tx.GetHash(), i, "VerifySignature returned false");
|
||||
}
|
||||
}
|
||||
++nBlocksScanned;
|
||||
if (nBlocksScanned % 1000 == 0)
|
||||
printf("auditsignatures: scanned %d blocks, %lld inputs, %lld failures\n",
|
||||
nBlocksScanned, (long long)nInputsChecked, (long long)nInputsFailed);
|
||||
}
|
||||
|
||||
Object result;
|
||||
result.push_back(Pair("start_height", start));
|
||||
result.push_back(Pair("end_height", end));
|
||||
result.push_back(Pair("blocks_scanned", nBlocksScanned));
|
||||
result.push_back(Pair("inputs_checked", nInputsChecked));
|
||||
result.push_back(Pair("inputs_failed", nInputsFailed));
|
||||
result.push_back(Pair("failures", failures));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// triangles: get information of sync-checkpoint
|
||||
Value getcheckpoint(const Array& params, bool fHelp)
|
||||
@@ -596,7 +708,7 @@ Value getblockchaininfo(const Array& params, bool fHelp)
|
||||
obj.push_back(Pair("difficulty", diff));
|
||||
|
||||
obj.push_back(Pair("moneysupply", ValueFromAmount(pindexBest->nMoneySupply)));
|
||||
obj.push_back(Pair("timeoffset", (boost::int64_t)GetTimeOffset()));
|
||||
obj.push_back(Pair("timeoffset", (int64_t)GetTimeOffset()));
|
||||
obj.push_back(Pair("connections", (int)vNodes.size()));
|
||||
obj.push_back(Pair("errors", GetWarnings("statusbar")));
|
||||
return obj;
|
||||
@@ -706,7 +818,7 @@ Value getaddressbalance(const Array& params, bool fHelp)
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address: " + strAddr);
|
||||
|
||||
int64_t nBalance = 0;
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
txdb.ReadAddressBalance(nType, hashBytes, nBalance);
|
||||
nTotalBalance += nBalance;
|
||||
}
|
||||
@@ -741,7 +853,7 @@ Value getaddressutxos(const Array& params, bool fHelp)
|
||||
Array addrArray = find_value(addrObj, "addresses").get_array();
|
||||
|
||||
Array result;
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
|
||||
for (unsigned int i = 0; i < addrArray.size(); i++)
|
||||
{
|
||||
@@ -801,7 +913,7 @@ Value getaddresstxids(const Array& params, bool fHelp)
|
||||
nEndHeight = endVal.get_int();
|
||||
|
||||
Array result;
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
|
||||
// Use a set to deduplicate txids across multiple addresses
|
||||
std::set<uint256> setTxIds;
|
||||
@@ -907,7 +1019,7 @@ Value invalidateblock(const Array& params, bool fHelp)
|
||||
|
||||
if (pindex->IsInMainChain())
|
||||
{
|
||||
CTxDB txdb;
|
||||
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
|
||||
if (!txdb.TxnBegin())
|
||||
throw runtime_error("Failed to begin transaction.");
|
||||
|
||||
@@ -930,7 +1042,7 @@ Value invalidateblock(const Array& params, bool fHelp)
|
||||
setStakeSeen.erase(make_pair(pindexWalk->prevoutStake, pindexWalk->nStakeTime));
|
||||
}
|
||||
|
||||
pindexWalk->pprev->pnext = NULL;
|
||||
pindexWalk->pprev->pnext = nullptr;
|
||||
pindexWalk = pindexWalk->pprev;
|
||||
}
|
||||
|
||||
@@ -976,7 +1088,7 @@ Value reconsiderblock(const Array& params, bool fHelp)
|
||||
if (!block.ReadFromDisk(pindex))
|
||||
throw runtime_error("Failed to read block from disk.");
|
||||
|
||||
CTxDB txdb;
|
||||
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
|
||||
block.SetBestChain(txdb, pindex);
|
||||
printf("reconsiderblock: reconsidered block %s at height %d, new best height=%d\n",
|
||||
hash.ToString().c_str(), pindex->nHeight, nBestHeight);
|
||||
@@ -1016,7 +1128,7 @@ Value dumputxoset(const Array& params, bool fHelp)
|
||||
if (nHeaders < 100)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "nheaders must be at least 100");
|
||||
|
||||
boost::filesystem::path destPath(filename);
|
||||
std::filesystem::path destPath(filename);
|
||||
std::string strError;
|
||||
|
||||
if (!UtxoSnapshot::DumpSnapshot(destPath, nHeaders, strError))
|
||||
@@ -1024,8 +1136,8 @@ Value dumputxoset(const Array& params, bool fHelp)
|
||||
|
||||
// Get file size
|
||||
int64_t nFileSize = 0;
|
||||
if (boost::filesystem::exists(destPath))
|
||||
nFileSize = (int64_t)boost::filesystem::file_size(destPath);
|
||||
if (std::filesystem::exists(destPath))
|
||||
nFileSize = (int64_t)std::filesystem::file_size(destPath);
|
||||
|
||||
Object result;
|
||||
result.push_back(Pair("filename", filename));
|
||||
|
||||
+6
-8
@@ -11,7 +11,6 @@
|
||||
#include "base58.h"
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#define printf OutputDebugStringF
|
||||
|
||||
@@ -94,9 +93,9 @@ public:
|
||||
bool fSpent;
|
||||
CWalletTx* ptx;
|
||||
int nOut;
|
||||
CTxDump(CWalletTx* ptx = NULL, int nOut = -1)
|
||||
CTxDump(CWalletTx* ptx = nullptr, int nOut = -1)
|
||||
{
|
||||
pindex = NULL;
|
||||
pindex = nullptr;
|
||||
nValue = 0;
|
||||
fSpent = false;
|
||||
this->ptx = ptx;
|
||||
@@ -167,8 +166,7 @@ Value importwallet(const Array& params, bool fHelp)
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
std::vector<std::string> vstr;
|
||||
boost::split(vstr, line, boost::is_any_of(" "));
|
||||
auto vstr = SplitString(line, ' ');
|
||||
if (vstr.size() < 2)
|
||||
continue;
|
||||
CTrianglesSecret vchSecret;
|
||||
@@ -189,13 +187,13 @@ Value importwallet(const Array& params, bool fHelp)
|
||||
std::string strLabel;
|
||||
bool fLabel = true;
|
||||
for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
|
||||
if (boost::algorithm::starts_with(vstr[nStr], "#"))
|
||||
if (vstr[nStr].starts_with("#"))
|
||||
break;
|
||||
if (vstr[nStr] == "change=1")
|
||||
fLabel = false;
|
||||
if (vstr[nStr] == "reserve=1")
|
||||
fLabel = false;
|
||||
if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
|
||||
if (vstr[nStr].starts_with("label=")) {
|
||||
strLabel = DecodeDumpString(vstr[nStr].substr(6));
|
||||
fLabel = true;
|
||||
}
|
||||
@@ -281,7 +279,7 @@ Value dumpwallet(const Array& params, bool fHelp)
|
||||
// sort time/key pairs
|
||||
std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
|
||||
for (std::map<CKeyID, int64_t>::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) {
|
||||
vKeyBirth.push_back(std::make_pair(it->second, it->first));
|
||||
vKeyBirth.push_back({it->second, it->first});
|
||||
}
|
||||
mapKeyBirth.clear();
|
||||
std::sort(vKeyBirth.begin(), vKeyBirth.end());
|
||||
|
||||
+8
-74
@@ -6,7 +6,6 @@
|
||||
#include "net.h"
|
||||
#include "addrman.h"
|
||||
#include "trianglesrpc.h"
|
||||
#include "alert.h"
|
||||
#include "wallet.h"
|
||||
#include "db.h"
|
||||
#include "walletdb.h"
|
||||
@@ -32,7 +31,7 @@ Value getnetworkinfo(const Array& params, bool fHelp)
|
||||
healthObj.push_back(Pair("torpeers", health.torPeers));
|
||||
healthObj.push_back(Pair("bootstrapped", health.isBootstrapped));
|
||||
healthObj.push_back(Pair("syncing", health.isSyncing));
|
||||
healthObj.push_back(Pair("lastblocktime", static_cast<boost::int64_t>(health.lastBlockTime)));
|
||||
healthObj.push_back(Pair("lastblocktime", static_cast<int64_t>(health.lastBlockTime)));
|
||||
healthObj.push_back(Pair("networkmode", "tor_native"));
|
||||
|
||||
Object obj;
|
||||
@@ -89,9 +88,9 @@ Value getpeerinfo(const Array& params, bool fHelp)
|
||||
|
||||
obj.push_back(Pair("addr", stats.addrName));
|
||||
obj.push_back(Pair("services", strprintf("%08"PRIx64, stats.nServices)));
|
||||
obj.push_back(Pair("lastsend", (boost::int64_t)stats.nLastSend));
|
||||
obj.push_back(Pair("lastrecv", (boost::int64_t)stats.nLastRecv));
|
||||
obj.push_back(Pair("conntime", (boost::int64_t)stats.nTimeConnected));
|
||||
obj.push_back(Pair("lastsend", (int64_t)stats.nLastSend));
|
||||
obj.push_back(Pair("lastrecv", (int64_t)stats.nLastRecv));
|
||||
obj.push_back(Pair("conntime", (int64_t)stats.nTimeConnected));
|
||||
obj.push_back(Pair("version", stats.nVersion));
|
||||
obj.push_back(Pair("subver", stats.strSubVer));
|
||||
obj.push_back(Pair("inbound", stats.fInbound));
|
||||
@@ -107,71 +106,6 @@ Value getpeerinfo(const Array& params, bool fHelp)
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern CCriticalSection cs_mapAlerts;
|
||||
extern map<uint256, CAlert> mapAlerts;
|
||||
|
||||
// triangles: send alert.
|
||||
// There is a known deadlock situation with ThreadMessageHandler
|
||||
// ThreadMessageHandler: holds cs_vSend and acquiring cs_main in SendMessages()
|
||||
// ThreadRPCServer: holds cs_main and acquiring cs_vSend in alert.RelayTo()/PushMessage()/BeginMessage()
|
||||
Value sendalert(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() < 6)
|
||||
throw runtime_error(
|
||||
"sendalert <message> <privatekey> <minver> <maxver> <priority> <id> [cancelupto]\n"
|
||||
"<message> is the alert text message\n"
|
||||
"<privatekey> is hex string of alert master private key\n"
|
||||
"<minver> is the minimum applicable internal client version\n"
|
||||
"<maxver> is the maximum applicable internal client version\n"
|
||||
"<priority> is integer priority number\n"
|
||||
"<id> is the alert id\n"
|
||||
"[cancelupto] cancels all alert id's up to this number\n"
|
||||
"Returns true or false.");
|
||||
|
||||
CAlert alert;
|
||||
CKey key;
|
||||
|
||||
alert.strStatusBar = params[0].get_str();
|
||||
alert.nMinVer = params[2].get_int();
|
||||
alert.nMaxVer = params[3].get_int();
|
||||
alert.nPriority = params[4].get_int();
|
||||
alert.nID = params[5].get_int();
|
||||
if (params.size() > 6)
|
||||
alert.nCancel = params[6].get_int();
|
||||
alert.nVersion = PROTOCOL_VERSION;
|
||||
alert.nRelayUntil = GetAdjustedTime() + 365*24*60*60;
|
||||
alert.nExpiration = GetAdjustedTime() + 365*24*60*60;
|
||||
|
||||
CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION);
|
||||
sMsg << (CUnsignedAlert)alert;
|
||||
alert.vchMsg = vector<unsigned char>(sMsg.begin(), sMsg.end());
|
||||
|
||||
vector<unsigned char> vchPrivKey = ParseHex(params[1].get_str());
|
||||
key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash
|
||||
if (!key.Sign(Hash(alert.vchMsg.begin(), alert.vchMsg.end()), alert.vchSig))
|
||||
throw runtime_error(
|
||||
"Unable to sign alert, check private key?\n");
|
||||
if(!alert.ProcessAlert())
|
||||
throw runtime_error(
|
||||
"Failed to process alert.\n");
|
||||
// Relay alert
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
alert.RelayTo(pnode);
|
||||
}
|
||||
|
||||
Object result;
|
||||
result.push_back(Pair("strStatusBar", alert.strStatusBar));
|
||||
result.push_back(Pair("nVersion", alert.nVersion));
|
||||
result.push_back(Pair("nMinVer", alert.nMinVer));
|
||||
result.push_back(Pair("nMaxVer", alert.nMaxVer));
|
||||
result.push_back(Pair("nPriority", alert.nPriority));
|
||||
result.push_back(Pair("nID", alert.nID));
|
||||
if (alert.nCancel > 0)
|
||||
result.push_back(Pair("nCancel", alert.nCancel));
|
||||
return result;
|
||||
}
|
||||
|
||||
Value addnode(const Array& params, bool fHelp)
|
||||
{
|
||||
@@ -261,7 +195,7 @@ Value getseedlist(const Array& params, bool fHelp)
|
||||
Object obj;
|
||||
obj.push_back(Pair("address", addr.ToStringIP()));
|
||||
obj.push_back(Pair("port", (int)addr.GetPort()));
|
||||
obj.push_back(Pair("lastseen", (boost::int64_t)addr.nTime));
|
||||
obj.push_back(Pair("lastseen", (int64_t)addr.nTime));
|
||||
ret.push_back(obj);
|
||||
}
|
||||
|
||||
@@ -340,11 +274,11 @@ Value getnetworkstability(const Array& params, bool fHelp)
|
||||
obj.push_back(Pair("ping", pingObj));
|
||||
|
||||
Object uptimeObj;
|
||||
uptimeObj.push_back(Pair("newest_sec", nTotal > 0 ? (boost::int64_t)nNewestConnection : 0));
|
||||
uptimeObj.push_back(Pair("oldest_sec", nTotal > 0 ? (boost::int64_t)nOldestConnection : 0));
|
||||
uptimeObj.push_back(Pair("newest_sec", nTotal > 0 ? (int64_t)nNewestConnection : 0));
|
||||
uptimeObj.push_back(Pair("oldest_sec", nTotal > 0 ? (int64_t)nOldestConnection : 0));
|
||||
obj.push_back(Pair("connection_uptime", uptimeObj));
|
||||
|
||||
obj.push_back(Pair("seconds_since_last_block", (boost::int64_t)(GetTime() - nTimeBestReceived)));
|
||||
obj.push_back(Pair("seconds_since_last_block", (int64_t)(GetTime() - nTimeBestReceived)));
|
||||
obj.push_back(Pair("current_height", nBestHeight));
|
||||
|
||||
return obj;
|
||||
|
||||
+12
-12
@@ -17,7 +17,7 @@ using namespace json_spirit;
|
||||
|
||||
void ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out, bool fIncludeHex)
|
||||
{
|
||||
txnouttype type;
|
||||
TxnOutType type;
|
||||
vector<CTxDestination> addresses;
|
||||
int nRequired;
|
||||
|
||||
@@ -28,7 +28,7 @@ void ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out, bool fIncludeH
|
||||
|
||||
if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired))
|
||||
{
|
||||
out.push_back(Pair("type", GetTxnOutputType(TX_NONSTANDARD)));
|
||||
out.push_back(Pair("type", GetTxnOutputType(TxnOutType::NonStandard)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry)
|
||||
{
|
||||
entry.push_back(Pair("txid", tx.GetHash().GetHex()));
|
||||
entry.push_back(Pair("version", tx.nVersion));
|
||||
entry.push_back(Pair("time", (boost::int64_t)tx.nTime));
|
||||
entry.push_back(Pair("locktime", (boost::int64_t)tx.nLockTime));
|
||||
entry.push_back(Pair("time", (int64_t)tx.nTime));
|
||||
entry.push_back(Pair("locktime", (int64_t)tx.nLockTime));
|
||||
Array vin;
|
||||
for (const CTxIn& txin : tx.vin)
|
||||
{
|
||||
@@ -56,13 +56,13 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry)
|
||||
else
|
||||
{
|
||||
in.push_back(Pair("txid", txin.prevout.hash.GetHex()));
|
||||
in.push_back(Pair("vout", (boost::int64_t)txin.prevout.n));
|
||||
in.push_back(Pair("vout", (int64_t)txin.prevout.n));
|
||||
Object o;
|
||||
o.push_back(Pair("asm", txin.scriptSig.ToString()));
|
||||
o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())));
|
||||
in.push_back(Pair("scriptSig", o));
|
||||
}
|
||||
in.push_back(Pair("sequence", (boost::int64_t)txin.nSequence));
|
||||
in.push_back(Pair("sequence", (int64_t)txin.nSequence));
|
||||
vin.push_back(in);
|
||||
}
|
||||
entry.push_back(Pair("vin", vin));
|
||||
@@ -72,7 +72,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry)
|
||||
const CTxOut& txout = tx.vout[i];
|
||||
Object out;
|
||||
out.push_back(Pair("value", ValueFromAmount(txout.nValue)));
|
||||
out.push_back(Pair("n", (boost::int64_t)i));
|
||||
out.push_back(Pair("n", (int64_t)i));
|
||||
Object o;
|
||||
ScriptPubKeyToJSON(txout.scriptPubKey, o, false);
|
||||
out.push_back(Pair("scriptPubKey", o));
|
||||
@@ -90,8 +90,8 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry)
|
||||
if (pindex->IsInMainChain())
|
||||
{
|
||||
entry.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight));
|
||||
entry.push_back(Pair("time", (boost::int64_t)pindex->nTime));
|
||||
entry.push_back(Pair("blocktime", (boost::int64_t)pindex->nTime));
|
||||
entry.push_back(Pair("time", (int64_t)pindex->nTime));
|
||||
entry.push_back(Pair("blocktime", (int64_t)pindex->nTime));
|
||||
}
|
||||
else
|
||||
entry.push_back(Pair("confirmations", 0));
|
||||
@@ -371,7 +371,7 @@ Value signrawtransaction(const Array& params, bool fHelp)
|
||||
CTransaction tempTx;
|
||||
MapPrevTx mapPrevTx;
|
||||
MapPrevTx mapEmpty;
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
bool fInvalid;
|
||||
|
||||
// FetchInputs aborts on failure, so we go one at a time.
|
||||
@@ -548,11 +548,11 @@ Value sendrawtransaction(const Array& params, bool fHelp)
|
||||
else
|
||||
{
|
||||
// push to local node
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
if (!tx.AcceptToMemoryPool(txdb))
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX rejected");
|
||||
|
||||
SyncWithWallets(tx, NULL, true);
|
||||
SyncWithWallets(tx, nullptr, true);
|
||||
}
|
||||
RelayTransaction(tx, hashTx);
|
||||
|
||||
|
||||
+14
-12
@@ -10,6 +10,8 @@
|
||||
#include "smessage.h"
|
||||
#include "init.h" // pwalletMain
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
using namespace json_spirit;
|
||||
using namespace std;
|
||||
|
||||
@@ -607,7 +609,7 @@ Value smsginbox(const Array& params, bool fHelp)
|
||||
{
|
||||
dbInbox.TxnBegin();
|
||||
|
||||
leveldb::Iterator* it = dbInbox.pdb->NewIterator(leveldb::ReadOptions());
|
||||
rocksdb::Iterator* it = dbInbox.pdb->NewIterator(rocksdb::ReadOptions());
|
||||
while (dbInbox.NextSmesgKey(it, sPrefix, chKey))
|
||||
{
|
||||
dbInbox.EraseSmesg(chKey);
|
||||
@@ -629,7 +631,7 @@ Value smsginbox(const Array& params, bool fHelp)
|
||||
|
||||
dbInbox.TxnBegin();
|
||||
|
||||
leveldb::Iterator* it = dbInbox.pdb->NewIterator(leveldb::ReadOptions());
|
||||
rocksdb::Iterator* it = dbInbox.pdb->NewIterator(rocksdb::ReadOptions());
|
||||
while (dbInbox.NextSmesg(it, sPrefix, chKey, smsgStored))
|
||||
{
|
||||
if (fCheckReadStatus
|
||||
@@ -717,7 +719,7 @@ Value smsgoutbox(const Array& params, bool fHelp)
|
||||
{
|
||||
dbOutbox.TxnBegin();
|
||||
|
||||
leveldb::Iterator* it = dbOutbox.pdb->NewIterator(leveldb::ReadOptions());
|
||||
rocksdb::Iterator* it = dbOutbox.pdb->NewIterator(rocksdb::ReadOptions());
|
||||
while (dbOutbox.NextSmesgKey(it, sPrefix, chKey))
|
||||
{
|
||||
dbOutbox.EraseSmesg(chKey);
|
||||
@@ -734,7 +736,7 @@ Value smsgoutbox(const Array& params, bool fHelp)
|
||||
{
|
||||
SecMsgStored smsgStored;
|
||||
MessageData msg;
|
||||
leveldb::Iterator* it = dbOutbox.pdb->NewIterator(leveldb::ReadOptions());
|
||||
rocksdb::Iterator* it = dbOutbox.pdb->NewIterator(rocksdb::ReadOptions());
|
||||
while (dbOutbox.NextSmesg(it, sPrefix, chKey, smsgStored))
|
||||
{
|
||||
uint32_t nPayload = smsgStored.vchMessage.size() - SMSG_HDR_LEN;
|
||||
@@ -820,10 +822,10 @@ Value smsgbuckets(const Array& params, bool fHelp)
|
||||
objM.push_back(Pair("hash", sHash));
|
||||
objM.push_back(Pair("last changed", getTimeString(it->second.timeChanged, cbuf, sizeof(cbuf))));
|
||||
|
||||
boost::filesystem::path fullPath = GetDataDir() / "smsgStore" / sFile;
|
||||
std::filesystem::path fullPath = GetDataDir() / "smsgStore" / sFile;
|
||||
|
||||
|
||||
if (!boost::filesystem::exists(fullPath))
|
||||
if (!std::filesystem::exists(fullPath))
|
||||
{
|
||||
// -- If there is a file for an empty bucket something is wrong.
|
||||
if (tokenSet.size() == 0)
|
||||
@@ -835,10 +837,10 @@ Value smsgbuckets(const Array& params, bool fHelp)
|
||||
try {
|
||||
|
||||
uint64_t nFBytes = 0;
|
||||
nFBytes = boost::filesystem::file_size(fullPath);
|
||||
nFBytes = std::filesystem::file_size(fullPath);
|
||||
nBytes += nFBytes;
|
||||
objM.push_back(Pair("file size", fsReadable(nFBytes)));
|
||||
} catch (const boost::filesystem::filesystem_error& ex)
|
||||
} catch (const std::filesystem::filesystem_error& ex)
|
||||
{
|
||||
objM.push_back(Pair("file size, error", ex.what()));
|
||||
};
|
||||
@@ -871,9 +873,9 @@ Value smsgbuckets(const Array& params, bool fHelp)
|
||||
std::string sFile = std::to_string(it->first) + "_01.dat";
|
||||
|
||||
try {
|
||||
boost::filesystem::path fullPath = GetDataDir() / "smsgStore" / sFile;
|
||||
boost::filesystem::remove(fullPath);
|
||||
} catch (const boost::filesystem::filesystem_error& ex)
|
||||
std::filesystem::path fullPath = GetDataDir() / "smsgStore" / sFile;
|
||||
std::filesystem::remove(fullPath);
|
||||
} catch (const std::filesystem::filesystem_error& ex)
|
||||
{
|
||||
//objM.push_back(Pair("file size, error", ex.what()));
|
||||
printf("Error removing bucket file %s.\n", ex.what());
|
||||
@@ -930,7 +932,7 @@ Value smsgbroadcast(const Array& params, bool fHelp)
|
||||
|
||||
// Iterate all "pk" entries
|
||||
std::string sPrefix("pk");
|
||||
leveldb::Iterator* it = dbPub.pdb->NewIterator(leveldb::ReadOptions());
|
||||
rocksdb::Iterator* it = dbPub.pdb->NewIterator(rocksdb::ReadOptions());
|
||||
for (it->Seek(sPrefix); it->Valid(); it->Next())
|
||||
{
|
||||
std::string key = it->key().ToString();
|
||||
|
||||
+14
-192
@@ -59,11 +59,11 @@ void WalletTxToJSON(const CWalletTx& wtx, Object& entry)
|
||||
entry.push_back(Pair("blockindex", wtx.nIndex));
|
||||
auto mi = mapBlockIndex.find(wtx.hashBlock);
|
||||
if (mi != mapBlockIndex.end() && mi->second)
|
||||
entry.push_back(Pair("blocktime", (boost::int64_t)(mi->second->nTime)));
|
||||
entry.push_back(Pair("blocktime", (int64_t)(mi->second->nTime)));
|
||||
}
|
||||
entry.push_back(Pair("txid", wtx.GetHash().GetHex()));
|
||||
entry.push_back(Pair("time", (boost::int64_t)wtx.GetTxTime()));
|
||||
entry.push_back(Pair("timereceived", (boost::int64_t)wtx.nTimeReceived));
|
||||
entry.push_back(Pair("time", (int64_t)wtx.GetTxTime()));
|
||||
entry.push_back(Pair("timereceived", (int64_t)wtx.nTimeReceived));
|
||||
for (const auto& item : wtx.mapValue)
|
||||
entry.push_back(Pair(item.first, item.second));
|
||||
}
|
||||
@@ -94,7 +94,7 @@ Value getinfo(const Array& params, bool fHelp)
|
||||
obj.push_back(Pair("newmint", ValueFromAmount(pwalletMain->GetNewMint())));
|
||||
obj.push_back(Pair("stake", ValueFromAmount(pwalletMain->GetStake())));
|
||||
obj.push_back(Pair("blocks", (int)nBestHeight));
|
||||
obj.push_back(Pair("timeoffset", (boost::int64_t)GetTimeOffset()));
|
||||
obj.push_back(Pair("timeoffset", (int64_t)GetTimeOffset()));
|
||||
obj.push_back(Pair("moneysupply", ValueFromAmount(pindexBest->nMoneySupply)));
|
||||
obj.push_back(Pair("connections", (int)vNodes.size()));
|
||||
obj.push_back(Pair("proxy", (proxy.first.IsValid() ? proxy.first.ToStringIPPort() : string())));
|
||||
@@ -105,14 +105,14 @@ Value getinfo(const Array& params, bool fHelp)
|
||||
obj.push_back(Pair("difficulty", diff));
|
||||
|
||||
obj.push_back(Pair("testnet", fTestNet));
|
||||
obj.push_back(Pair("keypoololdest", (boost::int64_t)pwalletMain->GetOldestKeyPoolTime()));
|
||||
obj.push_back(Pair("keypoololdest", (int64_t)pwalletMain->GetOldestKeyPoolTime()));
|
||||
obj.push_back(Pair("keypoolsize", (int)pwalletMain->GetKeyPoolSize()));
|
||||
obj.push_back(Pair("paytxfee", ValueFromAmount(nTransactionFee)));
|
||||
obj.push_back(Pair("mininput", ValueFromAmount(nMinimumInputValue)));
|
||||
if (pwalletMain->IsCrypted())
|
||||
{
|
||||
LOCK(cs_nWalletUnlockTime);
|
||||
obj.push_back(Pair("unlocked_until", (boost::int64_t)nWalletUnlockTime / 1000));
|
||||
obj.push_back(Pair("unlocked_until", (int64_t)nWalletUnlockTime / 1000));
|
||||
}
|
||||
obj.push_back(Pair("errors", GetWarnings("statusbar")));
|
||||
return obj;
|
||||
@@ -139,14 +139,14 @@ Value getwalletinfo(const Array& params, bool fHelp)
|
||||
obj.push_back(Pair("stake", ValueFromAmount(pwalletMain->GetStake())));
|
||||
obj.push_back(Pair("newmint", ValueFromAmount(pwalletMain->GetNewMint())));
|
||||
obj.push_back(Pair("txcount", txCount));
|
||||
obj.push_back(Pair("keypoololdest", (boost::int64_t)pwalletMain->GetOldestKeyPoolTime()));
|
||||
obj.push_back(Pair("keypoololdest", (int64_t)pwalletMain->GetOldestKeyPoolTime()));
|
||||
obj.push_back(Pair("keypoolsize", (int)pwalletMain->GetKeyPoolSize()));
|
||||
obj.push_back(Pair("paytxfee", ValueFromAmount(nTransactionFee)));
|
||||
obj.push_back(Pair("mininput", ValueFromAmount(nMinimumInputValue)));
|
||||
if (pwalletMain->IsCrypted())
|
||||
{
|
||||
LOCK(cs_nWalletUnlockTime);
|
||||
obj.push_back(Pair("unlocked_until", (boost::int64_t)nWalletUnlockTime / 1000));
|
||||
obj.push_back(Pair("unlocked_until", (int64_t)nWalletUnlockTime / 1000));
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@@ -818,7 +818,7 @@ Value sendmany(const Array& params, bool fHelp)
|
||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds");
|
||||
|
||||
// Send
|
||||
CReserveKey keyChange(pwalletMain);
|
||||
CReserveKey keyChange(pwalletMain.get());
|
||||
int64_t nFeeRequired = 0;
|
||||
bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired);
|
||||
if (!fCreated)
|
||||
@@ -1151,7 +1151,7 @@ void AcentryToJSON(const CAccountingEntry& acentry, const string& strAccount, Ar
|
||||
Object entry;
|
||||
entry.push_back(Pair("account", acentry.strAccount));
|
||||
entry.push_back(Pair("category", "move"));
|
||||
entry.push_back(Pair("time", (boost::int64_t)acentry.nTime));
|
||||
entry.push_back(Pair("time", (int64_t)acentry.nTime));
|
||||
entry.push_back(Pair("amount", ValueFromAmount(acentry.nCreditDebit)));
|
||||
entry.push_back(Pair("otheraccount", acentry.strOtherAccount));
|
||||
entry.push_back(Pair("comment", acentry.strComment));
|
||||
@@ -1284,7 +1284,7 @@ Value listsinceblock(const Array& params, bool fHelp)
|
||||
"listsinceblock [blockhash] [target-confirmations]\n"
|
||||
"Get all transactions in blocks since block [blockhash], or all transactions if omitted");
|
||||
|
||||
CBlockIndex *pindex = NULL;
|
||||
CBlockIndex *pindex = nullptr;
|
||||
int target_confirms = 1;
|
||||
|
||||
if (params.size() > 0)
|
||||
@@ -1535,7 +1535,7 @@ Value walletpassphrase(const Array& params, bool fHelp)
|
||||
"walletpassphrase <passphrase> <timeout>\n"
|
||||
"Stores the wallet decryption key in memory for <timeout> seconds.");
|
||||
|
||||
NewThread(ThreadTopUpKeyPool, NULL);
|
||||
NewThread(ThreadTopUpKeyPool, nullptr);
|
||||
int64_t* pnSleepTime = new int64_t(params[1].get_int64());
|
||||
NewThread(ThreadCleanWalletPassphrase, pnSleepTime);
|
||||
|
||||
@@ -1654,7 +1654,7 @@ public:
|
||||
CScript subscript;
|
||||
pwalletMain->GetCScript(scriptID, subscript);
|
||||
std::vector<CTxDestination> addresses;
|
||||
txnouttype whichType;
|
||||
TxnOutType whichType;
|
||||
int nRequired;
|
||||
ExtractDestinations(subscript, whichType, addresses, nRequired);
|
||||
obj.push_back(Pair("script", GetTxnOutputType(whichType)));
|
||||
@@ -1663,7 +1663,7 @@ public:
|
||||
for (const CTxDestination& addr : addresses)
|
||||
a.push_back(CTrianglesAddress(addr).ToString());
|
||||
obj.push_back(Pair("addresses", a));
|
||||
if (whichType == TX_MULTISIG)
|
||||
if (whichType == TxnOutType::MultiSig)
|
||||
obj.push_back(Pair("sigsrequired", nRequired));
|
||||
return obj;
|
||||
}
|
||||
@@ -1858,181 +1858,3 @@ Value makekeypair(const Array& params, bool fHelp)
|
||||
result.push_back(Pair("PublicKey", HexStr(key.GetPubKey().Raw())));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Value clearwallettransactions(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() > 0)
|
||||
throw runtime_error(
|
||||
"clearwallettransactions \n"
|
||||
"delete all transactions from wallet - reload with scanforalltxns\n"
|
||||
"Warning: Backup your wallet first!");
|
||||
|
||||
|
||||
|
||||
Object result;
|
||||
|
||||
uint32_t nTransactions = 0;
|
||||
|
||||
char cbuf[256];
|
||||
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
CWalletDB walletdb(pwalletMain->strWalletFile);
|
||||
walletdb.TxnBegin();
|
||||
Dbc* pcursor = walletdb.GetTxnCursor();
|
||||
if (!pcursor)
|
||||
throw runtime_error("Cannot get wallet DB cursor");
|
||||
|
||||
// RAII guard ensures cursor is closed even on exception
|
||||
struct CursorGuard {
|
||||
Dbc* cur;
|
||||
CursorGuard(Dbc* c) : cur(c) {}
|
||||
~CursorGuard() { if (cur) cur->close(); }
|
||||
} cursorGuard(pcursor);
|
||||
|
||||
Dbt datKey;
|
||||
Dbt datValue;
|
||||
|
||||
datKey.set_flags(DB_DBT_USERMEM);
|
||||
datValue.set_flags(DB_DBT_USERMEM);
|
||||
|
||||
std::vector<unsigned char> vchKey;
|
||||
std::vector<unsigned char> vchType;
|
||||
std::vector<unsigned char> vchKeyData;
|
||||
std::vector<unsigned char> vchValueData;
|
||||
|
||||
vchKeyData.resize(100);
|
||||
vchValueData.resize(100);
|
||||
|
||||
datKey.set_ulen(vchKeyData.size());
|
||||
datKey.set_data(&vchKeyData[0]);
|
||||
|
||||
datValue.set_ulen(vchValueData.size());
|
||||
datValue.set_data(&vchValueData[0]);
|
||||
|
||||
unsigned int fFlags = DB_NEXT; // same as using DB_FIRST for new cursor
|
||||
while (true)
|
||||
{
|
||||
int ret = pcursor->get(&datKey, &datValue, fFlags);
|
||||
|
||||
if (ret == ENOMEM
|
||||
|| ret == DB_BUFFER_SMALL)
|
||||
{
|
||||
if (datKey.get_size() > datKey.get_ulen())
|
||||
{
|
||||
vchKeyData.resize(datKey.get_size());
|
||||
datKey.set_ulen(vchKeyData.size());
|
||||
datKey.set_data(&vchKeyData[0]);
|
||||
};
|
||||
|
||||
if (datValue.get_size() > datValue.get_ulen())
|
||||
{
|
||||
vchValueData.resize(datValue.get_size());
|
||||
datValue.set_ulen(vchValueData.size());
|
||||
datValue.set_data(&vchValueData[0]);
|
||||
};
|
||||
// -- try once more, when DB_BUFFER_SMALL cursor is not expected to move
|
||||
ret = pcursor->get(&datKey, &datValue, fFlags);
|
||||
};
|
||||
|
||||
if (ret == DB_NOTFOUND)
|
||||
break;
|
||||
else
|
||||
if (datKey.get_data() == NULL || datValue.get_data() == NULL
|
||||
|| ret != 0)
|
||||
{
|
||||
const char* dbErr = db_strerror(ret);
|
||||
snprintf(cbuf, sizeof(cbuf), "wallet DB error %d, %s", ret, dbErr ? dbErr : "unknown");
|
||||
throw runtime_error(cbuf);
|
||||
};
|
||||
|
||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
||||
ssValue.SetType(SER_DISK);
|
||||
ssValue.clear();
|
||||
ssValue.write((char*)datKey.get_data(), datKey.get_size());
|
||||
|
||||
ssValue >> vchType;
|
||||
|
||||
|
||||
std::string strType(vchType.begin(), vchType.end());
|
||||
|
||||
//printf("strType %s\n", strType.c_str());
|
||||
|
||||
if (strType == "tx")
|
||||
{
|
||||
uint256 hash;
|
||||
ssValue >> hash;
|
||||
|
||||
if ((ret = pcursor->del(0)) != 0)
|
||||
{
|
||||
printf("Delete transaction failed %d, %s\n", ret, db_strerror(ret));
|
||||
continue;
|
||||
};
|
||||
|
||||
pwalletMain->mapWallet.erase(hash);
|
||||
try { pwalletMain->NotifyTransactionChanged(pwalletMain, hash, CT_DELETED); }
|
||||
catch (...) {
|
||||
printf("clearwallettransactions: NotifyTransactionChanged failed\n");
|
||||
}
|
||||
|
||||
nTransactions++;
|
||||
};
|
||||
};
|
||||
cursorGuard.cur = nullptr; // mark as handled
|
||||
pcursor->close();
|
||||
walletdb.TxnCommit();
|
||||
}
|
||||
|
||||
snprintf(cbuf, sizeof(cbuf), "Removed %u transactions.", nTransactions);
|
||||
result.push_back(Pair("complete", std::string(cbuf)));
|
||||
result.push_back(Pair("", "Reload with scanforstealthtxns or re-download blockchain."));
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Value scanforalltxns(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() > 1)
|
||||
throw runtime_error(
|
||||
"scanforalltxns [fromHeight]\n"
|
||||
"Scan blockchain for owned transactions.");
|
||||
|
||||
Object result;
|
||||
int32_t nFromHeight = 0;
|
||||
|
||||
CBlockIndex *pindex = pindexGenesisBlock;
|
||||
|
||||
|
||||
if (params.size() > 0)
|
||||
nFromHeight = params[0].get_int();
|
||||
|
||||
|
||||
if (nFromHeight > 0)
|
||||
{
|
||||
pindex = mapBlockIndex[hashBestChain];
|
||||
while (pindex->nHeight > nFromHeight
|
||||
&& pindex->pprev)
|
||||
pindex = pindex->pprev;
|
||||
};
|
||||
|
||||
if (pindex == NULL)
|
||||
throw runtime_error("Genesis Block is not set.");
|
||||
|
||||
{
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
pwalletMain->MarkDirty();
|
||||
|
||||
pwalletMain->ScanForWalletTransactions(pindex, true);
|
||||
pwalletMain->ReacceptWalletTransactions();
|
||||
}
|
||||
|
||||
result.push_back(Pair("result", "Scan complete."));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+73
-79
@@ -16,7 +16,7 @@ using namespace std;
|
||||
#include "sync.h"
|
||||
#include "util.h"
|
||||
|
||||
bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType);
|
||||
bool CheckSig(const vector<unsigned char>& vchSig, const vector<unsigned char>& vchPubKey, const CScript& scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType);
|
||||
|
||||
static const valtype vchFalse(0);
|
||||
static const valtype vchZero(0);
|
||||
@@ -93,17 +93,17 @@ static inline void popstack(vector<valtype>& stack)
|
||||
}
|
||||
|
||||
|
||||
const char* GetTxnOutputType(txnouttype t)
|
||||
const char* GetTxnOutputType(TxnOutType t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case TX_NONSTANDARD: return "nonstandard";
|
||||
case TX_PUBKEY: return "pubkey";
|
||||
case TX_PUBKEYHASH: return "pubkeyhash";
|
||||
case TX_SCRIPTHASH: return "scripthash";
|
||||
case TX_MULTISIG: return "multisig";
|
||||
case TxnOutType::NonStandard: return "nonstandard";
|
||||
case TxnOutType::PubKey: return "pubkey";
|
||||
case TxnOutType::PubKeyHash: return "pubkeyhash";
|
||||
case TxnOutType::ScriptHash: return "scripthash";
|
||||
case TxnOutType::MultiSig: return "multisig";
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -751,8 +751,8 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, co
|
||||
if (stack.size() < 1)
|
||||
return false;
|
||||
valtype& vch = stacktop(-1);
|
||||
for (unsigned int i = 0; i < vch.size(); i++)
|
||||
vch[i] = ~vch[i];
|
||||
for (auto& b : vch)
|
||||
b = ~b;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -893,7 +893,7 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, co
|
||||
break;
|
||||
|
||||
case OP_DIV:
|
||||
if (!BN_div(bn.get(), NULL, bn1.get(), bn2.get(), pctx))
|
||||
if (!BN_div(bn.get(), nullptr, bn1.get(), bn2.get(), pctx))
|
||||
return false;
|
||||
break;
|
||||
|
||||
@@ -973,7 +973,11 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, co
|
||||
valtype& vch = stacktop(-1);
|
||||
valtype vchHash((opcode == OP_RIPEMD160 || opcode == OP_SHA1 || opcode == OP_HASH160) ? 20 : 32);
|
||||
if (opcode == OP_RIPEMD160)
|
||||
{
|
||||
TRI_OPENSSL_SUPPRESS_DEPRECATED_BEGIN
|
||||
RIPEMD160(&vch[0], vch.size(), &vchHash[0]);
|
||||
TRI_OPENSSL_SUPPRESS_DEPRECATED_END
|
||||
}
|
||||
else if (opcode == OP_SHA1)
|
||||
SHA1(&vch[0], vch.size(), &vchHash[0]);
|
||||
else if (opcode == OP_SHA256)
|
||||
@@ -1227,7 +1231,7 @@ private:
|
||||
// Mix sighash with first 8 bytes of sig and pubkey for a fast key
|
||||
uint64_t k = hash.Get64();
|
||||
if (vchSig.size() >= 8)
|
||||
memcpy(&k, &k, 4); // keep upper half
|
||||
k = (k & 0xffffffff00000000ULL) | (k & 0x00000000ffffffffULL);
|
||||
k ^= std::hash<size_t>()(vchSig.size()) * 0x9e3779b97f4a7c15ULL;
|
||||
k ^= std::hash<size_t>()(vchPubKey.size()) * 0x517cc1b727220a95ULL;
|
||||
// Mix in actual signature bytes for uniqueness
|
||||
@@ -1271,7 +1275,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CScript scriptCode,
|
||||
bool CheckSig(const vector<unsigned char>& vchSig, const vector<unsigned char>& vchPubKey, const CScript& scriptCode,
|
||||
const CTransaction& txTo, unsigned int nIn, int nHashType)
|
||||
{
|
||||
static CSignatureCache signatureCache;
|
||||
@@ -1283,18 +1287,20 @@ bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CSc
|
||||
nHashType = vchSig.back();
|
||||
else if (nHashType != vchSig.back())
|
||||
return false;
|
||||
vchSig.pop_back();
|
||||
|
||||
vector<unsigned char> vchSigCopy(vchSig);
|
||||
vchSigCopy.pop_back();
|
||||
|
||||
uint256 sighash = SignatureHash(scriptCode, txTo, nIn, nHashType);
|
||||
|
||||
if (signatureCache.Get(sighash, vchSig, vchPubKey))
|
||||
if (signatureCache.Get(sighash, vchSigCopy, vchPubKey))
|
||||
return true;
|
||||
|
||||
CKey key;
|
||||
if (!key.SetPubKey(vchPubKey))
|
||||
return false;
|
||||
|
||||
if (!key.Verify(sighash, vchSig))
|
||||
if (!key.Verify(sighash, vchSigCopy))
|
||||
return false;
|
||||
|
||||
signatureCache.Set(sighash, vchSig, vchPubKey);
|
||||
@@ -1312,27 +1318,21 @@ bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CSc
|
||||
//
|
||||
// Return public keys or hashes from scriptPubKey, for 'standard' transaction types.
|
||||
//
|
||||
bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector<vector<unsigned char> >& vSolutionsRet)
|
||||
bool Solver(const CScript& scriptPubKey, TxnOutType& typeRet, vector<vector<unsigned char> >& vSolutionsRet)
|
||||
{
|
||||
// Templates
|
||||
static map<txnouttype, CScript> mTemplates;
|
||||
static map<TxnOutType, CScript> mTemplates;
|
||||
if (mTemplates.empty())
|
||||
{
|
||||
// Standard tx, sender provides pubkey, receiver adds signature
|
||||
mTemplates.insert(make_pair(TX_PUBKEY, CScript() << OP_PUBKEY << OP_CHECKSIG));
|
||||
|
||||
// Triangles address tx, sender provides hash of pubkey, receiver provides signature and pubkey
|
||||
mTemplates.insert(make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG));
|
||||
|
||||
// Sender provides N pubkeys, receivers provides M signatures
|
||||
mTemplates.insert(make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG));
|
||||
mTemplates.insert(make_pair(TxnOutType::PubKey, CScript() << OP_PUBKEY << OP_CHECKSIG));
|
||||
mTemplates.insert(make_pair(TxnOutType::PubKeyHash, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG));
|
||||
mTemplates.insert(make_pair(TxnOutType::MultiSig, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG));
|
||||
}
|
||||
|
||||
// Shortcut for pay-to-script-hash, which are more constrained than the other types:
|
||||
// it is always OP_HASH160 20 [20 byte hash] OP_EQUAL
|
||||
if (scriptPubKey.IsPayToScriptHash())
|
||||
{
|
||||
typeRet = TX_SCRIPTHASH;
|
||||
typeRet = TxnOutType::ScriptHash;
|
||||
vector<unsigned char> hashBytes(scriptPubKey.begin()+2, scriptPubKey.begin()+22);
|
||||
vSolutionsRet.push_back(hashBytes);
|
||||
return true;
|
||||
@@ -1357,7 +1357,7 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector<vector<unsi
|
||||
{
|
||||
// Found a match
|
||||
typeRet = tplate.first;
|
||||
if (typeRet == TX_MULTISIG)
|
||||
if (typeRet == TxnOutType::MultiSig)
|
||||
{
|
||||
// Additional checks for TX_MULTISIG:
|
||||
unsigned char m = vSolutionsRet.front()[0];
|
||||
@@ -1419,7 +1419,7 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector<vector<unsi
|
||||
}
|
||||
|
||||
vSolutionsRet.clear();
|
||||
typeRet = TX_NONSTANDARD;
|
||||
typeRet = TxnOutType::NonStandard;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1460,7 +1460,7 @@ bool SignN(const vector<valtype>& multisigdata, const CKeyStore& keystore, uint2
|
||||
// Returns false if scriptPubKey could not be completely satisfied.
|
||||
//
|
||||
bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash, int nHashType,
|
||||
CScript& scriptSigRet, txnouttype& whichTypeRet)
|
||||
CScript& scriptSigRet, TxnOutType& whichTypeRet)
|
||||
{
|
||||
scriptSigRet.clear();
|
||||
|
||||
@@ -1471,12 +1471,12 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash
|
||||
CKeyID keyID;
|
||||
switch (whichTypeRet)
|
||||
{
|
||||
case TX_NONSTANDARD:
|
||||
case TxnOutType::NonStandard:
|
||||
return false;
|
||||
case TX_PUBKEY:
|
||||
case TxnOutType::PubKey:
|
||||
keyID = CPubKey(vSolutions[0]).GetID();
|
||||
return Sign1(keyID, keystore, hash, nHashType, scriptSigRet);
|
||||
case TX_PUBKEYHASH:
|
||||
case TxnOutType::PubKeyHash:
|
||||
keyID = CKeyID(uint160(vSolutions[0]));
|
||||
if (!Sign1(keyID, keystore, hash, nHashType, scriptSigRet))
|
||||
return false;
|
||||
@@ -1487,32 +1487,32 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash
|
||||
scriptSigRet << vch;
|
||||
}
|
||||
return true;
|
||||
case TX_SCRIPTHASH:
|
||||
case TxnOutType::ScriptHash:
|
||||
return keystore.GetCScript(uint160(vSolutions[0]), scriptSigRet);
|
||||
|
||||
case TX_MULTISIG:
|
||||
scriptSigRet << OP_0; // workaround CHECKMULTISIG bug
|
||||
case TxnOutType::MultiSig:
|
||||
scriptSigRet << OP_0;
|
||||
return (SignN(vSolutions, keystore, hash, nHashType, scriptSigRet));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int ScriptSigArgsExpected(txnouttype t, const std::vector<std::vector<unsigned char> >& vSolutions)
|
||||
int ScriptSigArgsExpected(TxnOutType t, const std::vector<std::vector<unsigned char> >& vSolutions)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case TX_NONSTANDARD:
|
||||
case TxnOutType::NonStandard:
|
||||
return -1;
|
||||
case TX_PUBKEY:
|
||||
case TxnOutType::PubKey:
|
||||
return 1;
|
||||
case TX_PUBKEYHASH:
|
||||
case TxnOutType::PubKeyHash:
|
||||
return 2;
|
||||
case TX_MULTISIG:
|
||||
case TxnOutType::MultiSig:
|
||||
if (vSolutions.size() < 1 || vSolutions[0].size() < 1)
|
||||
return -1;
|
||||
return vSolutions[0][0] + 1;
|
||||
case TX_SCRIPTHASH:
|
||||
return 1; // doesn't include args needed by the script
|
||||
case TxnOutType::ScriptHash:
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -1520,11 +1520,11 @@ int ScriptSigArgsExpected(txnouttype t, const std::vector<std::vector<unsigned c
|
||||
bool IsStandard(const CScript& scriptPubKey)
|
||||
{
|
||||
vector<valtype> vSolutions;
|
||||
txnouttype whichType;
|
||||
TxnOutType whichType;
|
||||
if (!Solver(scriptPubKey, whichType, vSolutions))
|
||||
return false;
|
||||
|
||||
if (whichType == TX_MULTISIG)
|
||||
if (whichType == TxnOutType::MultiSig)
|
||||
{
|
||||
unsigned char m = vSolutions.front()[0];
|
||||
unsigned char n = vSolutions.back()[0];
|
||||
@@ -1535,7 +1535,7 @@ bool IsStandard(const CScript& scriptPubKey)
|
||||
return false;
|
||||
}
|
||||
|
||||
return whichType != TX_NONSTANDARD;
|
||||
return whichType != TxnOutType::NonStandard;
|
||||
}
|
||||
|
||||
|
||||
@@ -1571,29 +1571,29 @@ bool IsMine(const CKeyStore &keystore, const CTxDestination &dest)
|
||||
bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
|
||||
{
|
||||
vector<valtype> vSolutions;
|
||||
txnouttype whichType;
|
||||
TxnOutType whichType;
|
||||
if (!Solver(scriptPubKey, whichType, vSolutions))
|
||||
return false;
|
||||
|
||||
CKeyID keyID;
|
||||
switch (whichType)
|
||||
{
|
||||
case TX_NONSTANDARD:
|
||||
case TxnOutType::NonStandard:
|
||||
return false;
|
||||
case TX_PUBKEY:
|
||||
case TxnOutType::PubKey:
|
||||
keyID = CPubKey(vSolutions[0]).GetID();
|
||||
return keystore.HaveKey(keyID);
|
||||
case TX_PUBKEYHASH:
|
||||
case TxnOutType::PubKeyHash:
|
||||
keyID = CKeyID(uint160(vSolutions[0]));
|
||||
return keystore.HaveKey(keyID);
|
||||
case TX_SCRIPTHASH:
|
||||
case TxnOutType::ScriptHash:
|
||||
{
|
||||
CScript subscript;
|
||||
if (!keystore.GetCScript(CScriptID(uint160(vSolutions[0])), subscript))
|
||||
return false;
|
||||
return IsMine(keystore, subscript);
|
||||
}
|
||||
case TX_MULTISIG:
|
||||
case TxnOutType::MultiSig:
|
||||
{
|
||||
// Only consider transactions "mine" if we own ALL the
|
||||
// keys involved. multi-signature transactions that are
|
||||
@@ -1610,21 +1610,21 @@ bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
|
||||
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
|
||||
{
|
||||
vector<valtype> vSolutions;
|
||||
txnouttype whichType;
|
||||
TxnOutType whichType;
|
||||
if (!Solver(scriptPubKey, whichType, vSolutions))
|
||||
return false;
|
||||
|
||||
if (whichType == TX_PUBKEY)
|
||||
if (whichType == TxnOutType::PubKey)
|
||||
{
|
||||
addressRet = CPubKey(vSolutions[0]).GetID();
|
||||
return true;
|
||||
}
|
||||
else if (whichType == TX_PUBKEYHASH)
|
||||
else if (whichType == TxnOutType::PubKeyHash)
|
||||
{
|
||||
addressRet = CKeyID(uint160(vSolutions[0]));
|
||||
return true;
|
||||
}
|
||||
else if (whichType == TX_SCRIPTHASH)
|
||||
else if (whichType == TxnOutType::ScriptHash)
|
||||
{
|
||||
addressRet = CScriptID(uint160(vSolutions[0]));
|
||||
return true;
|
||||
@@ -1642,7 +1642,7 @@ public:
|
||||
CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {}
|
||||
|
||||
void Process(const CScript &script) {
|
||||
txnouttype type;
|
||||
TxnOutType type;
|
||||
std::vector<CTxDestination> vDest;
|
||||
int nRequired;
|
||||
if (ExtractDestinations(script, type, vDest, nRequired)) {
|
||||
@@ -1671,15 +1671,15 @@ void ExtractAffectedKeys(const CKeyStore &keystore, const CScript& scriptPubKey,
|
||||
CAffectedKeysVisitor(keystore, vKeys).Process(scriptPubKey);
|
||||
}
|
||||
|
||||
bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, vector<CTxDestination>& addressRet, int& nRequiredRet)
|
||||
bool ExtractDestinations(const CScript& scriptPubKey, TxnOutType& typeRet, vector<CTxDestination>& addressRet, int& nRequiredRet)
|
||||
{
|
||||
addressRet.clear();
|
||||
typeRet = TX_NONSTANDARD;
|
||||
typeRet = TxnOutType::NonStandard;
|
||||
vector<valtype> vSolutions;
|
||||
if (!Solver(scriptPubKey, typeRet, vSolutions))
|
||||
return false;
|
||||
|
||||
if (typeRet == TX_MULTISIG)
|
||||
if (typeRet == TxnOutType::MultiSig)
|
||||
{
|
||||
nRequiredRet = vSolutions.front()[0];
|
||||
for (unsigned int i = 1; i < vSolutions.size()-1; i++)
|
||||
@@ -1747,23 +1747,19 @@ bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CTransa
|
||||
// The checksig op will also drop the signatures from its hash.
|
||||
uint256 hash = SignatureHash(fromPubKey, txTo, nIn, nHashType);
|
||||
|
||||
txnouttype whichType;
|
||||
TxnOutType whichType;
|
||||
if (!Solver(keystore, fromPubKey, hash, nHashType, txin.scriptSig, whichType))
|
||||
return false;
|
||||
|
||||
if (whichType == TX_SCRIPTHASH)
|
||||
if (whichType == TxnOutType::ScriptHash)
|
||||
{
|
||||
// Solver returns the subscript that need to be evaluated;
|
||||
// the final scriptSig is the signatures from that
|
||||
// and then the serialized subscript:
|
||||
CScript subscript = txin.scriptSig;
|
||||
|
||||
// Recompute txn hash using subscript in place of scriptPubKey:
|
||||
uint256 hash2 = SignatureHash(subscript, txTo, nIn, nHashType);
|
||||
|
||||
txnouttype subType;
|
||||
TxnOutType subType;
|
||||
bool fSolved =
|
||||
Solver(keystore, subscript, hash2, nHashType, txin.scriptSig, subType) && subType != TX_SCRIPTHASH;
|
||||
Solver(keystore, subscript, hash2, nHashType, txin.scriptSig, subType) && subType != TxnOutType::ScriptHash;
|
||||
// Append serialized subscript whether or not it is completely signed:
|
||||
txin.scriptSig << static_cast<valtype>(subscript);
|
||||
if (!fSolved) return false;
|
||||
@@ -1862,23 +1858,21 @@ static CScript CombineMultisig(CScript scriptPubKey, const CTransaction& txTo, u
|
||||
}
|
||||
|
||||
static CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn,
|
||||
const txnouttype txType, const vector<valtype>& vSolutions,
|
||||
const TxnOutType txType, const vector<valtype>& vSolutions,
|
||||
vector<valtype>& sigs1, vector<valtype>& sigs2)
|
||||
{
|
||||
switch (txType)
|
||||
{
|
||||
case TX_NONSTANDARD:
|
||||
// Don't know anything about this, assume bigger one is correct:
|
||||
case TxnOutType::NonStandard:
|
||||
if (sigs1.size() >= sigs2.size())
|
||||
return PushAll(sigs1);
|
||||
return PushAll(sigs2);
|
||||
case TX_PUBKEY:
|
||||
case TX_PUBKEYHASH:
|
||||
// Signatures are bigger than placeholders or empty scripts:
|
||||
case TxnOutType::PubKey:
|
||||
case TxnOutType::PubKeyHash:
|
||||
if (sigs1.empty() || sigs1[0].empty())
|
||||
return PushAll(sigs2);
|
||||
return PushAll(sigs1);
|
||||
case TX_SCRIPTHASH:
|
||||
case TxnOutType::ScriptHash:
|
||||
if (sigs1.empty() || sigs1.back().empty())
|
||||
return PushAll(sigs2);
|
||||
else if (sigs2.empty() || sigs2.back().empty())
|
||||
@@ -1889,7 +1883,7 @@ static CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo,
|
||||
valtype spk = sigs1.back();
|
||||
CScript pubKey2(spk.begin(), spk.end());
|
||||
|
||||
txnouttype txType2;
|
||||
TxnOutType txType2;
|
||||
vector<vector<unsigned char> > vSolutions2;
|
||||
Solver(pubKey2, txType2, vSolutions2);
|
||||
sigs1.pop_back();
|
||||
@@ -1898,7 +1892,7 @@ static CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo,
|
||||
result << spk;
|
||||
return result;
|
||||
}
|
||||
case TX_MULTISIG:
|
||||
case TxnOutType::MultiSig:
|
||||
return CombineMultisig(scriptPubKey, txTo, nIn, vSolutions, sigs1, sigs2);
|
||||
}
|
||||
|
||||
@@ -1908,7 +1902,7 @@ static CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo,
|
||||
CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn,
|
||||
const CScript& scriptSig1, const CScript& scriptSig2)
|
||||
{
|
||||
txnouttype txType;
|
||||
TxnOutType txType;
|
||||
vector<vector<unsigned char> > vSolutions;
|
||||
Solver(scriptPubKey, txType, vSolutions);
|
||||
|
||||
|
||||
+20
-21
@@ -16,7 +16,7 @@
|
||||
#include "keystore.h"
|
||||
#include "bignum.h"
|
||||
|
||||
typedef std::vector<unsigned char> valtype;
|
||||
using valtype = std::vector<unsigned char>;
|
||||
|
||||
class CTransaction;
|
||||
|
||||
@@ -30,14 +30,13 @@ enum
|
||||
};
|
||||
|
||||
|
||||
enum txnouttype
|
||||
enum class TxnOutType
|
||||
{
|
||||
TX_NONSTANDARD,
|
||||
// 'standard' transaction types:
|
||||
TX_PUBKEY,
|
||||
TX_PUBKEYHASH,
|
||||
TX_SCRIPTHASH,
|
||||
TX_MULTISIG,
|
||||
NonStandard,
|
||||
PubKey,
|
||||
PubKeyHash,
|
||||
ScriptHash,
|
||||
MultiSig,
|
||||
};
|
||||
|
||||
class CNoDestination {
|
||||
@@ -52,9 +51,9 @@ public:
|
||||
* * CScriptID: TX_SCRIPTHASH destination
|
||||
* A CTxDestination is the internal data type encoded in a CTrianglesAddress
|
||||
*/
|
||||
typedef std::variant<CNoDestination, CKeyID, CScriptID> CTxDestination;
|
||||
using CTxDestination = std::variant<CNoDestination, CKeyID, CScriptID>;
|
||||
|
||||
const char* GetTxnOutputType(txnouttype t);
|
||||
const char* GetTxnOutputType(TxnOutType t);
|
||||
|
||||
/** Script opcodes */
|
||||
enum opcodetype
|
||||
@@ -267,7 +266,7 @@ protected:
|
||||
|
||||
public:
|
||||
CScript() { }
|
||||
CScript(const CScript& b) : std::vector<unsigned char>(b.begin(), b.end()) { }
|
||||
CScript(const CScript& b) = default;
|
||||
CScript(const_iterator pbegin, const_iterator pend) : std::vector<unsigned char>(pbegin, pend) { }
|
||||
#ifndef _MSC_VER
|
||||
CScript(const unsigned char* pbegin, const unsigned char* pend) : std::vector<unsigned char>(pbegin, pend) { }
|
||||
@@ -590,19 +589,19 @@ public:
|
||||
|
||||
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType);
|
||||
bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet);
|
||||
int ScriptSigArgsExpected(txnouttype t, const std::vector<std::vector<unsigned char> >& vSolutions);
|
||||
bool IsStandard(const CScript& scriptPubKey);
|
||||
bool IsMine(const CKeyStore& keystore, const CScript& scriptPubKey);
|
||||
bool IsMine(const CKeyStore& keystore, const CTxDestination &dest);
|
||||
bool Solver(const CScript& scriptPubKey, TxnOutType& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet);
|
||||
int ScriptSigArgsExpected(TxnOutType t, const std::vector<std::vector<unsigned char> >& vSolutions);
|
||||
[[nodiscard]] bool IsStandard(const CScript& scriptPubKey);
|
||||
[[nodiscard]] bool IsMine(const CKeyStore& keystore, const CScript& scriptPubKey);
|
||||
[[nodiscard]] bool IsMine(const CKeyStore& keystore, const CTxDestination &dest);
|
||||
void ExtractAffectedKeys(const CKeyStore &keystore, const CScript& scriptPubKey, std::vector<CKeyID> &vKeys);
|
||||
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet);
|
||||
bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet);
|
||||
bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL);
|
||||
bool SignSignature(const CKeyStore& keystore, const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL);
|
||||
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn,
|
||||
bool ExtractDestinations(const CScript& scriptPubKey, TxnOutType& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet);
|
||||
[[nodiscard]] bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL);
|
||||
[[nodiscard]] bool SignSignature(const CKeyStore& keystore, const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL);
|
||||
[[nodiscard]] bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn,
|
||||
int nHashType);
|
||||
bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int nHashType);
|
||||
[[nodiscard]] bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int nHashType);
|
||||
|
||||
// Given two sets of signatures for scriptPubKey, possibly with OP_0 placeholders,
|
||||
// combine them intelligently and return the result.
|
||||
|
||||
Submodule
+1
Submodule src/secp256k1 added at 1a53f4961f
+33
-35
@@ -11,12 +11,13 @@
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <cassert>
|
||||
#include <ios>
|
||||
#include <limits>
|
||||
#include <stdint.h>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
#include <boost/type_traits/is_fundamental.hpp>
|
||||
#include <type_traits>
|
||||
#include <tuple>
|
||||
|
||||
#include "allocators.h"
|
||||
@@ -58,12 +59,11 @@ enum
|
||||
unsigned int GetSerializeSize(int nType, int nVersion) const \
|
||||
{ \
|
||||
CSerActionGetSerializeSize ser_action; \
|
||||
const bool fGetSize = true; \
|
||||
const bool fWrite = false; \
|
||||
const bool fRead = false; \
|
||||
[[maybe_unused]] const bool fGetSize = true; \
|
||||
[[maybe_unused]] const bool fWrite = false; \
|
||||
[[maybe_unused]] const bool fRead = false; \
|
||||
unsigned int nSerSize = 0; \
|
||||
ser_streamplaceholder s; \
|
||||
assert(fGetSize||fWrite||fRead); /* suppress warning */ \
|
||||
s.nType = nType; \
|
||||
s.nVersion = nVersion; \
|
||||
{statements} \
|
||||
@@ -73,22 +73,20 @@ enum
|
||||
void Serialize(Stream& s, int nType, int nVersion) const \
|
||||
{ \
|
||||
CSerActionSerialize ser_action; \
|
||||
const bool fGetSize = false; \
|
||||
const bool fWrite = true; \
|
||||
const bool fRead = false; \
|
||||
unsigned int nSerSize = 0; \
|
||||
assert(fGetSize||fWrite||fRead); /* suppress warning */ \
|
||||
[[maybe_unused]] const bool fGetSize = false; \
|
||||
[[maybe_unused]] const bool fWrite = true; \
|
||||
[[maybe_unused]] const bool fRead = false; \
|
||||
[[maybe_unused]] unsigned int nSerSize = 0; \
|
||||
{statements} \
|
||||
} \
|
||||
template<typename Stream> \
|
||||
void Unserialize(Stream& s, int nType, int nVersion) \
|
||||
{ \
|
||||
CSerActionUnserialize ser_action; \
|
||||
const bool fGetSize = false; \
|
||||
const bool fWrite = false; \
|
||||
const bool fRead = true; \
|
||||
unsigned int nSerSize = 0; \
|
||||
assert(fGetSize||fWrite||fRead); /* suppress warning */ \
|
||||
[[maybe_unused]] const bool fGetSize = false; \
|
||||
[[maybe_unused]] const bool fWrite = false; \
|
||||
[[maybe_unused]] const bool fRead = true; \
|
||||
[[maybe_unused]] unsigned int nSerSize = 0; \
|
||||
{statements} \
|
||||
}
|
||||
|
||||
@@ -287,14 +285,14 @@ template<typename Stream, typename C> void Serialize(Stream& os, const std::basi
|
||||
template<typename Stream, typename C> void Unserialize(Stream& is, std::basic_string<C>& str, int, int=0);
|
||||
|
||||
// vector
|
||||
template<typename T, typename A> unsigned int GetSerializeSize_impl(const std::vector<T, A>& v, int nType, int nVersion, const boost::true_type&);
|
||||
template<typename T, typename A> unsigned int GetSerializeSize_impl(const std::vector<T, A>& v, int nType, int nVersion, const boost::false_type&);
|
||||
template<typename T, typename A> unsigned int GetSerializeSize_impl(const std::vector<T, A>& v, int nType, int nVersion, const std::true_type&);
|
||||
template<typename T, typename A> unsigned int GetSerializeSize_impl(const std::vector<T, A>& v, int nType, int nVersion, const std::false_type&);
|
||||
template<typename T, typename A> inline unsigned int GetSerializeSize(const std::vector<T, A>& v, int nType, int nVersion);
|
||||
template<typename Stream, typename T, typename A> void Serialize_impl(Stream& os, const std::vector<T, A>& v, int nType, int nVersion, const boost::true_type&);
|
||||
template<typename Stream, typename T, typename A> void Serialize_impl(Stream& os, const std::vector<T, A>& v, int nType, int nVersion, const boost::false_type&);
|
||||
template<typename Stream, typename T, typename A> void Serialize_impl(Stream& os, const std::vector<T, A>& v, int nType, int nVersion, const std::true_type&);
|
||||
template<typename Stream, typename T, typename A> void Serialize_impl(Stream& os, const std::vector<T, A>& v, int nType, int nVersion, const std::false_type&);
|
||||
template<typename Stream, typename T, typename A> inline void Serialize(Stream& os, const std::vector<T, A>& v, int nType, int nVersion);
|
||||
template<typename Stream, typename T, typename A> void Unserialize_impl(Stream& is, std::vector<T, A>& v, int nType, int nVersion, const boost::true_type&);
|
||||
template<typename Stream, typename T, typename A> void Unserialize_impl(Stream& is, std::vector<T, A>& v, int nType, int nVersion, const boost::false_type&);
|
||||
template<typename Stream, typename T, typename A> void Unserialize_impl(Stream& is, std::vector<T, A>& v, int nType, int nVersion, const std::true_type&);
|
||||
template<typename Stream, typename T, typename A> void Unserialize_impl(Stream& is, std::vector<T, A>& v, int nType, int nVersion, const std::false_type&);
|
||||
template<typename Stream, typename T, typename A> inline void Unserialize(Stream& is, std::vector<T, A>& v, int nType, int nVersion);
|
||||
|
||||
// others derived from vector
|
||||
@@ -391,13 +389,13 @@ void Unserialize(Stream& is, std::basic_string<C>& str, int, int)
|
||||
// vector
|
||||
//
|
||||
template<typename T, typename A>
|
||||
unsigned int GetSerializeSize_impl(const std::vector<T, A>& v, int nType, int nVersion, const boost::true_type&)
|
||||
unsigned int GetSerializeSize_impl(const std::vector<T, A>& v, int nType, int nVersion, const std::true_type&)
|
||||
{
|
||||
return (GetSizeOfCompactSize(v.size()) + v.size() * sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T, typename A>
|
||||
unsigned int GetSerializeSize_impl(const std::vector<T, A>& v, int nType, int nVersion, const boost::false_type&)
|
||||
unsigned int GetSerializeSize_impl(const std::vector<T, A>& v, int nType, int nVersion, const std::false_type&)
|
||||
{
|
||||
unsigned int nSize = GetSizeOfCompactSize(v.size());
|
||||
for (typename std::vector<T, A>::const_iterator vi = v.begin(); vi != v.end(); ++vi)
|
||||
@@ -408,12 +406,12 @@ unsigned int GetSerializeSize_impl(const std::vector<T, A>& v, int nType, int nV
|
||||
template<typename T, typename A>
|
||||
inline unsigned int GetSerializeSize(const std::vector<T, A>& v, int nType, int nVersion)
|
||||
{
|
||||
return GetSerializeSize_impl(v, nType, nVersion, boost::is_fundamental<T>());
|
||||
return GetSerializeSize_impl(v, nType, nVersion, std::is_fundamental<T>{});
|
||||
}
|
||||
|
||||
|
||||
template<typename Stream, typename T, typename A>
|
||||
void Serialize_impl(Stream& os, const std::vector<T, A>& v, int nType, int nVersion, const boost::true_type&)
|
||||
void Serialize_impl(Stream& os, const std::vector<T, A>& v, int nType, int nVersion, const std::true_type&)
|
||||
{
|
||||
WriteCompactSize(os, v.size());
|
||||
if (!v.empty())
|
||||
@@ -421,7 +419,7 @@ void Serialize_impl(Stream& os, const std::vector<T, A>& v, int nType, int nVers
|
||||
}
|
||||
|
||||
template<typename Stream, typename T, typename A>
|
||||
void Serialize_impl(Stream& os, const std::vector<T, A>& v, int nType, int nVersion, const boost::false_type&)
|
||||
void Serialize_impl(Stream& os, const std::vector<T, A>& v, int nType, int nVersion, const std::false_type&)
|
||||
{
|
||||
WriteCompactSize(os, v.size());
|
||||
for (typename std::vector<T, A>::const_iterator vi = v.begin(); vi != v.end(); ++vi)
|
||||
@@ -431,12 +429,12 @@ void Serialize_impl(Stream& os, const std::vector<T, A>& v, int nType, int nVers
|
||||
template<typename Stream, typename T, typename A>
|
||||
inline void Serialize(Stream& os, const std::vector<T, A>& v, int nType, int nVersion)
|
||||
{
|
||||
Serialize_impl(os, v, nType, nVersion, boost::is_fundamental<T>());
|
||||
Serialize_impl(os, v, nType, nVersion, std::is_fundamental<T>{});
|
||||
}
|
||||
|
||||
|
||||
template<typename Stream, typename T, typename A>
|
||||
void Unserialize_impl(Stream& is, std::vector<T, A>& v, int nType, int nVersion, const boost::true_type&)
|
||||
void Unserialize_impl(Stream& is, std::vector<T, A>& v, int nType, int nVersion, const std::true_type&)
|
||||
{
|
||||
// Limit size per read so bogus size value won't cause out of memory
|
||||
v.clear();
|
||||
@@ -452,7 +450,7 @@ void Unserialize_impl(Stream& is, std::vector<T, A>& v, int nType, int nVersion,
|
||||
}
|
||||
|
||||
template<typename Stream, typename T, typename A>
|
||||
void Unserialize_impl(Stream& is, std::vector<T, A>& v, int nType, int nVersion, const boost::false_type&)
|
||||
void Unserialize_impl(Stream& is, std::vector<T, A>& v, int nType, int nVersion, const std::false_type&)
|
||||
{
|
||||
v.clear();
|
||||
unsigned int nSize = ReadCompactSize(is);
|
||||
@@ -472,7 +470,7 @@ void Unserialize_impl(Stream& is, std::vector<T, A>& v, int nType, int nVersion,
|
||||
template<typename Stream, typename T, typename A>
|
||||
inline void Unserialize(Stream& is, std::vector<T, A>& v, int nType, int nVersion)
|
||||
{
|
||||
Unserialize_impl(is, v, nType, nVersion, boost::is_fundamental<T>());
|
||||
Unserialize_impl(is, v, nType, nVersion, std::is_fundamental<T>{});
|
||||
}
|
||||
|
||||
|
||||
@@ -704,7 +702,7 @@ struct ser_streamplaceholder
|
||||
|
||||
|
||||
|
||||
typedef std::vector<char, zero_after_free_allocator<char> > CSerializeData;
|
||||
using CSerializeData = std::vector<char, zero_after_free_allocator<char>>;
|
||||
|
||||
/** Double ended buffer combining vector and stream-like interfaces.
|
||||
*
|
||||
@@ -1059,18 +1057,18 @@ public:
|
||||
|
||||
void fclose()
|
||||
{
|
||||
if (file != NULL && file != stdin && file != stdout && file != stderr)
|
||||
if (file != nullptr && file != stdin && file != stdout && file != stderr)
|
||||
::fclose(file);
|
||||
file = NULL;
|
||||
file = nullptr;
|
||||
}
|
||||
|
||||
FILE* release() { FILE* ret = file; file = NULL; return ret; }
|
||||
FILE* release() { FILE* ret = file; file = nullptr; return ret; }
|
||||
operator FILE*() { return file; }
|
||||
FILE* operator->() { return file; }
|
||||
FILE& operator*() { return *file; }
|
||||
FILE** operator&() { return &file; }
|
||||
FILE* operator=(FILE* pnew) { return file = pnew; }
|
||||
bool operator!() { return (file == NULL); }
|
||||
bool operator!() { return (file == nullptr); }
|
||||
|
||||
|
||||
//
|
||||
|
||||
+289
-135
@@ -41,18 +41,15 @@ Notes:
|
||||
#include <errno.h>
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/ecdh.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
|
||||
#include <string>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
|
||||
#include "base58.h"
|
||||
#include "crypto_ecdh.h"
|
||||
#include "db.h"
|
||||
#include "init.h" // pwalletMain
|
||||
#include "txdb.h"
|
||||
@@ -60,6 +57,12 @@ Notes:
|
||||
|
||||
#include "lz4/lz4.h"
|
||||
|
||||
// LevelDB headers retained solely for the one-shot smsgDB leveldb→rocksdb
|
||||
// migration in MigrateSmsgDBLevelDbToRocksDb. Once all users have upgraded
|
||||
// past v5.10 the migration helper (and these includes) can be dropped.
|
||||
#include <leveldb/db.h>
|
||||
#include <leveldb/iterator.h>
|
||||
|
||||
#include "xxhash/xxhash.h"
|
||||
#include "xxhash/xxhash.c"
|
||||
|
||||
@@ -77,9 +80,9 @@ Notes:
|
||||
|
||||
// TODO: For buckets older than current, only need to store no. messages and hash in memory
|
||||
|
||||
boost::signals2::signal<void (SecMsgStored& inboxHdr)> NotifySecMsgInboxChanged;
|
||||
boost::signals2::signal<void (SecMsgStored& outboxHdr)> NotifySecMsgOutboxChanged;
|
||||
boost::signals2::signal<void ()> NotifySecMsgWalletUnlocked;
|
||||
CSignal<void(SecMsgStored&)> NotifySecMsgInboxChanged;
|
||||
CSignal<void(SecMsgStored&)> NotifySecMsgOutboxChanged;
|
||||
CSignal<void()> NotifySecMsgWalletUnlocked;
|
||||
|
||||
bool fSecMsgEnabled = false;
|
||||
|
||||
@@ -94,13 +97,163 @@ uint32_t nPeerIdCounter = 1;
|
||||
CCriticalSection cs_smsg;
|
||||
CCriticalSection cs_smsgDB;
|
||||
|
||||
leveldb::DB *smsgDB = NULL;
|
||||
rocksdb::DB *smsgDB = nullptr;
|
||||
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// rocksdb::DB::Open shipped the raw DB** overload for years, then added a
|
||||
// std::unique_ptr<DB>* form in 8.x and removed the raw form in newer
|
||||
// releases (Homebrew's macOS rocksdb hits this path; Ubuntu 22.04 and
|
||||
// MSYS2 still expose the DB** form). Pick whichever overload the linked
|
||||
// rocksdb actually has via SFINAE — `int` parameter is preferred over
|
||||
// `long`, so if DB** exists, the first overload wins; otherwise the
|
||||
// fallback that wraps unique_ptr is used.
|
||||
template<typename T>
|
||||
inline auto OpenSmsgDBImpl(const rocksdb::Options& opts, const std::string& path,
|
||||
T** dbptr, int)
|
||||
-> decltype(rocksdb::DB::Open(opts, path, dbptr))
|
||||
{
|
||||
return rocksdb::DB::Open(opts, path, dbptr);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline rocksdb::Status OpenSmsgDBImpl(const rocksdb::Options& opts, const std::string& path,
|
||||
T** dbptr, long)
|
||||
{
|
||||
std::unique_ptr<T> tmp;
|
||||
auto s = rocksdb::DB::Open(opts, path, &tmp);
|
||||
if (s.ok()) *dbptr = tmp.release();
|
||||
return s;
|
||||
}
|
||||
|
||||
inline rocksdb::Status OpenSmsgDB(const rocksdb::Options& opts,
|
||||
const std::string& path,
|
||||
rocksdb::DB** dbptr)
|
||||
{
|
||||
return OpenSmsgDBImpl(opts, path, dbptr, 0);
|
||||
}
|
||||
|
||||
// ── smsgDB leveldb → rocksdb migration ──────────────────────────────────────
|
||||
//
|
||||
// Pre-v5.10 the smessage store was backed by LevelDB at <datadir>/smsgDB/.
|
||||
// Phase 3a switched it to RocksDB. Existing installations need their pubkey
|
||||
// cache and inbox/outbox to carry across. The migration is one-shot and
|
||||
// runs lazily inside SecMsgDB::Open: detect the legacy format, rename the
|
||||
// directory aside as a backup, copy every key into a fresh rocksdb tree,
|
||||
// then proceed with the normal open path. The leveldb backup is preserved
|
||||
// (never deleted) so the user can roll back by deleting smsgDB/ and
|
||||
// renaming smsgDB.leveldb-backup/ back.
|
||||
|
||||
// LevelDB and RocksDB share several filenames (CURRENT, MANIFEST-*, LOG).
|
||||
// RocksDB additionally writes IDENTITY and OPTIONS-* on first open;
|
||||
// presence of CURRENT *without* IDENTITY indicates a legacy LevelDB tree.
|
||||
inline bool IsLegacyLevelDbSmsgDir(const fs::path& dir)
|
||||
{
|
||||
if (!fs::exists(dir / "CURRENT"))
|
||||
return false;
|
||||
if (fs::exists(dir / "IDENTITY"))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool MigrateSmsgDBLevelDbToRocksDb(std::string& strError)
|
||||
{
|
||||
fs::path smsgDir = GetDataDir() / "smsgDB";
|
||||
fs::path backupDir = GetDataDir() / "smsgDB.leveldb-backup";
|
||||
|
||||
if (fs::exists(backupDir)) {
|
||||
strError = "smsgDB.leveldb-backup/ already present at " + backupDir.string()
|
||||
+ " — manual cleanup required before retrying migration.";
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("smessage: migrating LevelDB-format smsgDB to RocksDB...\n");
|
||||
|
||||
// Atomic rename so the legacy data is never deleted by this routine —
|
||||
// worst case we leave the backup and bail. Filesystem rename within the
|
||||
// same datadir is atomic on every supported platform.
|
||||
std::error_code ec;
|
||||
fs::rename(smsgDir, backupDir, ec);
|
||||
if (ec) {
|
||||
strError = "Failed to rename smsgDB to backup: " + ec.message();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open backup as leveldb (read-only) — create_if_missing left at default
|
||||
// false so a malformed dir errors out cleanly.
|
||||
leveldb::Options leveldbOpts;
|
||||
leveldb::DB* oldDb = nullptr;
|
||||
leveldb::Status ls = leveldb::DB::Open(leveldbOpts, backupDir.string(), &oldDb);
|
||||
if (!ls.ok()) {
|
||||
strError = "Failed to open legacy LevelDB smsgDB at "
|
||||
+ backupDir.string() + ": " + ls.ToString();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open destination as fresh rocksdb.
|
||||
rocksdb::Options rdbOpts;
|
||||
rdbOpts.create_if_missing = true;
|
||||
rocksdb::DB* newDb = nullptr;
|
||||
rocksdb::Status rs = OpenSmsgDB(rdbOpts, smsgDir.string(), &newDb);
|
||||
if (!rs.ok()) {
|
||||
delete oldDb;
|
||||
strError = "Failed to create new RocksDB smsgDB: " + rs.ToString();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy every key in batches of 5000.
|
||||
leveldb::Iterator* it = oldDb->NewIterator(leveldb::ReadOptions());
|
||||
rocksdb::WriteBatch batch;
|
||||
int nMigrated = 0;
|
||||
int nBatch = 0;
|
||||
bool fOk = true;
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
batch.Put(it->key().ToString(), it->value().ToString());
|
||||
nBatch++;
|
||||
nMigrated++;
|
||||
if (nBatch >= 5000) {
|
||||
rocksdb::Status ws = newDb->Write(rocksdb::WriteOptions(), &batch);
|
||||
if (!ws.ok()) {
|
||||
strError = "RocksDB batch write failed during migration: " + ws.ToString();
|
||||
fOk = false;
|
||||
break;
|
||||
}
|
||||
batch.Clear();
|
||||
nBatch = 0;
|
||||
}
|
||||
}
|
||||
if (fOk && nBatch > 0) {
|
||||
rocksdb::Status ws = newDb->Write(rocksdb::WriteOptions(), &batch);
|
||||
if (!ws.ok()) {
|
||||
strError = "RocksDB final batch write failed: " + ws.ToString();
|
||||
fOk = false;
|
||||
}
|
||||
}
|
||||
if (fOk && !it->status().ok()) {
|
||||
strError = "LevelDB iterator failed mid-migration: " + it->status().ToString();
|
||||
fOk = false;
|
||||
}
|
||||
delete it;
|
||||
delete oldDb;
|
||||
delete newDb;
|
||||
|
||||
if (!fOk) {
|
||||
// Leave smsgDB/ in a partially-written state but the backup is
|
||||
// intact. The user can recover by removing smsgDB/ and renaming
|
||||
// smsgDB.leveldb-backup/ → smsgDB/.
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("smessage: migrated %d entries from LevelDB to RocksDB. "
|
||||
"Original data preserved at %s\n",
|
||||
nMigrated, backupDir.string().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -137,12 +290,12 @@ bool SecureMsgAllDigits(const std::string& value)
|
||||
|
||||
bool SecureMsgParseBucketFilename(const std::string& fileName, int64_t& bucket, uint32_t& fileIndex, bool& fWalletLocked)
|
||||
{
|
||||
if (!boost::algorithm::ends_with(fileName, ".dat"))
|
||||
if (!fileName.ends_with(".dat"))
|
||||
return false;
|
||||
|
||||
std::string baseName = fileName.substr(0, fileName.size() - 4);
|
||||
fWalletLocked = false;
|
||||
if (boost::algorithm::ends_with(baseName, "_wl"))
|
||||
if (baseName.ends_with("_wl"))
|
||||
{
|
||||
fWalletLocked = true;
|
||||
baseName.erase(baseName.size() - 3);
|
||||
@@ -211,7 +364,7 @@ void SecureMsgGetBucketFiles(const fs::path& pathSmsgDir, int64_t bucket, bool f
|
||||
|| fFileWalletLocked != fWalletLocked)
|
||||
continue;
|
||||
|
||||
bucketFiles.push_back(std::make_pair(fileIndex, (*itd).path()));
|
||||
bucketFiles.push_back({fileIndex, (*itd).path()});
|
||||
};
|
||||
|
||||
std::sort(bucketFiles.begin(), bucketFiles.end(),
|
||||
@@ -316,7 +469,7 @@ bool SecMsgCrypter::Encrypt(unsigned char* chPlaintext, uint32_t nPlain, std::ve
|
||||
|
||||
bool fOk = true;
|
||||
|
||||
if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, &chKey[0], &chIV[0]);
|
||||
if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, &chKey[0], &chIV[0]);
|
||||
if (fOk) fOk = EVP_EncryptUpdate(ctx, &vchCiphertext[0], &nCLen, chPlaintext, nLen);
|
||||
if (fOk) fOk = EVP_EncryptFinal_ex(ctx, (&vchCiphertext[0])+nCLen, &nFLen);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
@@ -345,7 +498,7 @@ bool SecMsgCrypter::Decrypt(unsigned char* chCiphertext, uint32_t nCipher, std::
|
||||
|
||||
bool fOk = true;
|
||||
|
||||
if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, &chKey[0], &chIV[0]);
|
||||
if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, &chKey[0], &chIV[0]);
|
||||
if (fOk) fOk = EVP_DecryptUpdate(ctx, &vchPlaintext[0], &nPLen, &chCiphertext[0], nCipher);
|
||||
if (fOk) fOk = EVP_DecryptFinal_ex(ctx, (&vchPlaintext[0])+nPLen, &nFLen);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
@@ -390,9 +543,9 @@ bool SecMsgDB::Open(const char* pszMode)
|
||||
};
|
||||
|
||||
bool fCreate = strchr(pszMode, 'c');
|
||||
|
||||
|
||||
fs::path fullpath = GetDataDir() / "smsgDB";
|
||||
|
||||
|
||||
if (!fCreate
|
||||
&& (!fs::exists(fullpath)
|
||||
|| !fs::is_directory(fullpath)))
|
||||
@@ -400,10 +553,21 @@ bool SecMsgDB::Open(const char* pszMode)
|
||||
printf("SecMsgDB::open() - DB does not exist.\n");
|
||||
return false;
|
||||
};
|
||||
|
||||
leveldb::Options options;
|
||||
|
||||
// One-shot migration: pre-v5.10 nodes have a LevelDB tree under smsgDB/.
|
||||
// Detect that and convert to RocksDB before opening. The legacy data is
|
||||
// renamed to smsgDB.leveldb-backup/ as a recovery option.
|
||||
if (fs::is_directory(fullpath) && IsLegacyLevelDbSmsgDir(fullpath)) {
|
||||
std::string migrateError;
|
||||
if (!MigrateSmsgDBLevelDbToRocksDb(migrateError)) {
|
||||
printf("SecMsgDB::open() - migration failed: %s\n", migrateError.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb::Options options;
|
||||
options.create_if_missing = fCreate;
|
||||
leveldb::Status s = leveldb::DB::Open(options, fullpath.string(), &smsgDB);
|
||||
rocksdb::Status s = OpenSmsgDB(options, fullpath.string(), &smsgDB);
|
||||
|
||||
if (!s.ok())
|
||||
{
|
||||
@@ -417,66 +581,37 @@ bool SecMsgDB::Open(const char* pszMode)
|
||||
};
|
||||
|
||||
|
||||
class SecMsgBatchScanner : public leveldb::WriteBatch::Handler
|
||||
{
|
||||
public:
|
||||
std::string needle;
|
||||
bool* deleted;
|
||||
std::string* foundValue;
|
||||
bool foundEntry;
|
||||
|
||||
SecMsgBatchScanner() : foundEntry(false) {}
|
||||
|
||||
virtual void Put(const leveldb::Slice& key, const leveldb::Slice& value)
|
||||
{
|
||||
if (key.ToString() == needle)
|
||||
{
|
||||
foundEntry = true;
|
||||
*deleted = false;
|
||||
*foundValue = value.ToString();
|
||||
};
|
||||
};
|
||||
|
||||
virtual void Delete(const leveldb::Slice& key)
|
||||
{
|
||||
if (key.ToString() == needle)
|
||||
{
|
||||
foundEntry = true;
|
||||
*deleted = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// When performing a read, if we have an active batch we need to check it first
|
||||
// before reading from the database, as the rest of the code assumes that once
|
||||
// a database transaction begins reads are consistent with it. It would be good
|
||||
// to change that assumption in future and avoid the performance hit, though in
|
||||
// practice it does not appear to be large.
|
||||
// a database transaction begins reads are consistent with it.
|
||||
//
|
||||
// Previously implemented via rocksdb::WriteBatch::Handler subclass, which fails
|
||||
// to link against Ubuntu's librocksdb-dev (typeinfo for the Handler base class
|
||||
// isn't exported there). The pendingBatch map is updated alongside every Put
|
||||
// or Delete on activeBatch and answers ScanBatch queries directly.
|
||||
bool SecMsgDB::ScanBatch(const CDataStream& key, std::string* value, bool* deleted) const
|
||||
{
|
||||
if (!activeBatch)
|
||||
return false;
|
||||
|
||||
|
||||
*deleted = false;
|
||||
SecMsgBatchScanner scanner;
|
||||
scanner.needle = key.str();
|
||||
scanner.deleted = deleted;
|
||||
scanner.foundValue = value;
|
||||
leveldb::Status s = activeBatch->Iterate(&scanner);
|
||||
if (!s.ok())
|
||||
{
|
||||
printf("SecMsgDB ScanBatch error: %s\n", s.ToString().c_str());
|
||||
auto it = pendingBatch.find(key.str());
|
||||
if (it == pendingBatch.end())
|
||||
return false;
|
||||
};
|
||||
|
||||
return scanner.foundEntry;
|
||||
if (!it->second.has_value()) {
|
||||
*deleted = true;
|
||||
return true;
|
||||
}
|
||||
*value = *it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SecMsgDB::TxnBegin()
|
||||
{
|
||||
if (activeBatch)
|
||||
return true;
|
||||
activeBatch = new leveldb::WriteBatch();
|
||||
activeBatch = new rocksdb::WriteBatch();
|
||||
pendingBatch.clear();
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -484,12 +619,13 @@ bool SecMsgDB::TxnCommit()
|
||||
{
|
||||
if (!activeBatch)
|
||||
return false;
|
||||
|
||||
leveldb::WriteOptions writeOptions;
|
||||
|
||||
rocksdb::WriteOptions writeOptions;
|
||||
writeOptions.sync = true;
|
||||
leveldb::Status status = pdb->Write(writeOptions, activeBatch);
|
||||
rocksdb::Status status = pdb->Write(writeOptions, activeBatch);
|
||||
delete activeBatch;
|
||||
activeBatch = NULL;
|
||||
activeBatch = nullptr;
|
||||
pendingBatch.clear();
|
||||
|
||||
if (!status.ok())
|
||||
{
|
||||
@@ -503,7 +639,8 @@ bool SecMsgDB::TxnCommit()
|
||||
bool SecMsgDB::TxnAbort()
|
||||
{
|
||||
delete activeBatch;
|
||||
activeBatch = NULL;
|
||||
activeBatch = nullptr;
|
||||
pendingBatch.clear();
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -531,12 +668,12 @@ bool SecMsgDB::ReadPK(CKeyID& addr, CPubKey& pubkey)
|
||||
|
||||
if (readFromDb)
|
||||
{
|
||||
leveldb::Status s = pdb->Get(leveldb::ReadOptions(), ssKey.str(), &strValue);
|
||||
rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &strValue);
|
||||
if (!s.ok())
|
||||
{
|
||||
if (s.IsNotFound())
|
||||
return false;
|
||||
printf("LevelDB read failure: %s\n", s.ToString().c_str());
|
||||
printf("RocksDB read failure: %s\n", s.ToString().c_str());
|
||||
return false;
|
||||
};
|
||||
};
|
||||
@@ -569,18 +706,19 @@ bool SecMsgDB::WritePK(CKeyID& addr, CPubKey& pubkey)
|
||||
if (activeBatch)
|
||||
{
|
||||
activeBatch->Put(ssKey.str(), ssValue.str());
|
||||
pendingBatch[ssKey.str()] = ssValue.str();
|
||||
return true;
|
||||
};
|
||||
|
||||
leveldb::WriteOptions writeOptions;
|
||||
|
||||
rocksdb::WriteOptions writeOptions;
|
||||
writeOptions.sync = true;
|
||||
leveldb::Status s = pdb->Put(writeOptions, ssKey.str(), ssValue.str());
|
||||
rocksdb::Status s = pdb->Put(writeOptions, ssKey.str(), ssValue.str());
|
||||
if (!s.ok())
|
||||
{
|
||||
printf("SecMsgDB write failure: %s\n", s.ToString().c_str());
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -605,12 +743,12 @@ bool SecMsgDB::ExistsPK(CKeyID& addr)
|
||||
};
|
||||
};
|
||||
|
||||
leveldb::Status s = pdb->Get(leveldb::ReadOptions(), ssKey.str(), &unused);
|
||||
rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &unused);
|
||||
return s.IsNotFound() == false;
|
||||
};
|
||||
|
||||
|
||||
bool SecMsgDB::NextSmesg(leveldb::Iterator* it, std::string& prefix, unsigned char* chKey, SecMsgStored& smsgStored)
|
||||
bool SecMsgDB::NextSmesg(rocksdb::Iterator* it, std::string& prefix, unsigned char* chKey, SecMsgStored& smsgStored)
|
||||
{
|
||||
if (!pdb)
|
||||
return false;
|
||||
@@ -638,7 +776,7 @@ bool SecMsgDB::NextSmesg(leveldb::Iterator* it, std::string& prefix, unsigned ch
|
||||
return true;
|
||||
};
|
||||
|
||||
bool SecMsgDB::NextSmesgKey(leveldb::Iterator* it, std::string& prefix, unsigned char* chKey)
|
||||
bool SecMsgDB::NextSmesgKey(rocksdb::Iterator* it, std::string& prefix, unsigned char* chKey)
|
||||
{
|
||||
if (!pdb)
|
||||
return false;
|
||||
@@ -679,12 +817,12 @@ bool SecMsgDB::ReadSmesg(unsigned char* chKey, SecMsgStored& smsgStored)
|
||||
|
||||
if (readFromDb)
|
||||
{
|
||||
leveldb::Status s = pdb->Get(leveldb::ReadOptions(), ssKey.str(), &strValue);
|
||||
rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &strValue);
|
||||
if (!s.ok())
|
||||
{
|
||||
if (s.IsNotFound())
|
||||
return false;
|
||||
printf("LevelDB read failure: %s\n", s.ToString().c_str());
|
||||
printf("RocksDB read failure: %s\n", s.ToString().c_str());
|
||||
return false;
|
||||
};
|
||||
};
|
||||
@@ -713,18 +851,19 @@ bool SecMsgDB::WriteSmesg(unsigned char* chKey, SecMsgStored& smsgStored)
|
||||
if (activeBatch)
|
||||
{
|
||||
activeBatch->Put(ssKey.str(), ssValue.str());
|
||||
pendingBatch[ssKey.str()] = ssValue.str();
|
||||
return true;
|
||||
};
|
||||
|
||||
leveldb::WriteOptions writeOptions;
|
||||
|
||||
rocksdb::WriteOptions writeOptions;
|
||||
writeOptions.sync = true;
|
||||
leveldb::Status s = pdb->Put(writeOptions, ssKey.str(), ssValue.str());
|
||||
rocksdb::Status s = pdb->Put(writeOptions, ssKey.str(), ssValue.str());
|
||||
if (!s.ok())
|
||||
{
|
||||
printf("SecMsgDB write failed: %s\n", s.ToString().c_str());
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -746,7 +885,7 @@ bool SecMsgDB::ExistsSmesg(unsigned char* chKey)
|
||||
};
|
||||
};
|
||||
|
||||
leveldb::Status s = pdb->Get(leveldb::ReadOptions(), ssKey.str(), &unused);
|
||||
rocksdb::Status s = pdb->Get(rocksdb::ReadOptions(), ssKey.str(), &unused);
|
||||
return s.IsNotFound() == false;
|
||||
return true;
|
||||
};
|
||||
@@ -759,12 +898,13 @@ bool SecMsgDB::EraseSmesg(unsigned char* chKey)
|
||||
if (activeBatch)
|
||||
{
|
||||
activeBatch->Delete(ssKey.str());
|
||||
pendingBatch[ssKey.str()] = std::nullopt;
|
||||
return true;
|
||||
};
|
||||
|
||||
leveldb::WriteOptions writeOptions;
|
||||
|
||||
rocksdb::WriteOptions writeOptions;
|
||||
writeOptions.sync = true;
|
||||
leveldb::Status s = pdb->Delete(writeOptions, ssKey.str());
|
||||
rocksdb::Status s = pdb->Delete(writeOptions, ssKey.str());
|
||||
|
||||
if (s.ok() || s.IsNotFound())
|
||||
return true;
|
||||
@@ -881,7 +1021,7 @@ void ThreadSecureMsgPow(void* parg)
|
||||
// -- sleep at end, then fSecMsgEnabled is tested on wake
|
||||
|
||||
SecMsgDB dbOutbox;
|
||||
leveldb::Iterator* it;
|
||||
rocksdb::Iterator* it;
|
||||
{
|
||||
LOCK(cs_smsgDB);
|
||||
|
||||
@@ -889,7 +1029,7 @@ void ThreadSecureMsgPow(void* parg)
|
||||
continue;
|
||||
|
||||
// -- fifo (smallest key first)
|
||||
it = dbOutbox.pdb->NewIterator(leveldb::ReadOptions());
|
||||
it = dbOutbox.pdb->NewIterator(rocksdb::ReadOptions());
|
||||
}
|
||||
// -- break up lock, SecureMsgSetHash will take long
|
||||
|
||||
@@ -1202,7 +1342,7 @@ int SecureMsgReadIni()
|
||||
continue;
|
||||
|
||||
if (!(pName = strtok(cLine, "="))
|
||||
|| !(pValue = strtok(NULL, "=")))
|
||||
|| !(pValue = strtok(nullptr, "=")))
|
||||
continue;
|
||||
|
||||
if (strcmp(pName, "newAddressRecv") == 0)
|
||||
@@ -1336,8 +1476,8 @@ bool SecureMsgStart(bool fDontStart, bool fScanChain)
|
||||
};
|
||||
|
||||
// -- start threads
|
||||
if (!NewThread(ThreadSecureMsg, NULL)
|
||||
|| !NewThread(ThreadSecureMsgPow, NULL))
|
||||
if (!NewThread(ThreadSecureMsg, nullptr)
|
||||
|| !NewThread(ThreadSecureMsgPow, nullptr))
|
||||
{
|
||||
printf("SecureMsg could not start threads, secure messaging disabled.\n");
|
||||
fSecMsgEnabled = false;
|
||||
@@ -1367,7 +1507,7 @@ bool SecureMsgShutdown()
|
||||
{
|
||||
LOCK(cs_smsgDB);
|
||||
delete smsgDB;
|
||||
smsgDB = NULL;
|
||||
smsgDB = nullptr;
|
||||
};
|
||||
|
||||
// -- main program will wait 5 seconds for threads to terminate.
|
||||
@@ -1411,8 +1551,8 @@ bool SecureMsgEnable()
|
||||
}; // LOCK(cs_smsg);
|
||||
|
||||
// -- start threads
|
||||
if (!NewThread(ThreadSecureMsg, NULL)
|
||||
|| !NewThread(ThreadSecureMsgPow, NULL))
|
||||
if (!NewThread(ThreadSecureMsg, nullptr)
|
||||
|| !NewThread(ThreadSecureMsgPow, nullptr))
|
||||
{
|
||||
printf("SecureMsgEnable could not start threads, secure messaging disabled.\n");
|
||||
fSecMsgEnabled = false;
|
||||
@@ -1482,7 +1622,7 @@ bool SecureMsgDisable()
|
||||
{
|
||||
LOCK(cs_smsgDB);
|
||||
delete smsgDB;
|
||||
smsgDB = NULL;
|
||||
smsgDB = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -2110,7 +2250,7 @@ int SecureMsgInsertAddress(CKeyID& hashKey, CPubKey& pubKey)
|
||||
};
|
||||
|
||||
|
||||
static bool ScanBlock(CBlock& block, CTxDB& txdb, SecMsgDB& addrpkdb,
|
||||
static bool ScanBlock(CBlock& block, CTxDBBase& txdb, SecMsgDB& addrpkdb,
|
||||
uint32_t& nTransactions, uint32_t& nInputs, uint32_t& nPubkeys, uint32_t& nDuplicates)
|
||||
{
|
||||
// -- should have LOCK(cs_smsg) where db is opened
|
||||
@@ -2238,7 +2378,7 @@ bool SecureMsgScanBlock(CBlock& block)
|
||||
|
||||
{
|
||||
LOCK(cs_smsgDB);
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
|
||||
SecMsgDB addrpkdb;
|
||||
if (!addrpkdb.Open("cw")
|
||||
@@ -2277,7 +2417,7 @@ bool ScanChainForPublicKeys(CBlockIndex* pindexStart)
|
||||
{
|
||||
LOCK(cs_smsgDB);
|
||||
|
||||
CTxDB txdb("r");
|
||||
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
|
||||
|
||||
SecMsgDB addrpkdb;
|
||||
if (!addrpkdb.Open("cw")
|
||||
@@ -2313,7 +2453,7 @@ bool SecureMsgScanBlockChain()
|
||||
if (lockMain)
|
||||
{
|
||||
CBlockIndex *pindexScan = pindexGenesisBlock;
|
||||
if (pindexScan == NULL)
|
||||
if (pindexScan == nullptr)
|
||||
{
|
||||
printf("Error: pindexGenesisBlock not set.\n");
|
||||
return false;
|
||||
@@ -2472,7 +2612,7 @@ bool SecureMsgScanBuckets()
|
||||
// -- remove wl file when scanned
|
||||
try {
|
||||
fs::remove((*itd).path());
|
||||
} catch (const boost::filesystem::filesystem_error& ex)
|
||||
} catch (const std::filesystem::filesystem_error& ex)
|
||||
{
|
||||
printf("Error removing wl file %s - %s\n", fileName.c_str(), ex.what());
|
||||
return 1;
|
||||
@@ -2552,7 +2692,7 @@ int SecureMsgWalletUnlocked()
|
||||
printf("Dropping wallet locked file %s, expired.\n", fileName.c_str());
|
||||
try {
|
||||
fs::remove((*itd).path());
|
||||
} catch (const boost::filesystem::filesystem_error& ex)
|
||||
} catch (const std::filesystem::filesystem_error& ex)
|
||||
{
|
||||
printf("Error removing wl file %s - %s\n", fileName.c_str(), ex.what());
|
||||
return 1;
|
||||
@@ -2620,7 +2760,7 @@ int SecureMsgWalletUnlocked()
|
||||
// -- remove wl file when scanned
|
||||
try {
|
||||
fs::remove((*itd).path());
|
||||
} catch (const boost::filesystem::filesystem_error& ex)
|
||||
} catch (const std::filesystem::filesystem_error& ex)
|
||||
{
|
||||
printf("Error removing wl file %s - %s\n", fileName.c_str(), ex.what());
|
||||
return 1;
|
||||
@@ -3119,7 +3259,7 @@ int SecureMsgStoreUnscanned(unsigned char *pHeader, unsigned char *pPayload, uin
|
||||
try {
|
||||
pathSmsgDir = GetDataDir() / "smsgStore";
|
||||
fs::create_directory(pathSmsgDir);
|
||||
} catch (const boost::filesystem::filesystem_error& ex)
|
||||
} catch (const std::filesystem::filesystem_error& ex)
|
||||
{
|
||||
printf("Error: Failed to create directory %s - %s\n", pathSmsgDir.string().c_str(), ex.what());
|
||||
return 1;
|
||||
@@ -3209,7 +3349,7 @@ int SecureMsgStore(unsigned char *pHeader, unsigned char *pPayload, uint32_t nPa
|
||||
try {
|
||||
pathSmsgDir = GetDataDir() / "smsgStore";
|
||||
fs::create_directory(pathSmsgDir);
|
||||
} catch (const boost::filesystem::filesystem_error& ex)
|
||||
} catch (const std::filesystem::filesystem_error& ex)
|
||||
{
|
||||
printf("Error: Failed to create directory %s - %s\n", pathSmsgDir.string().c_str(), ex.what());
|
||||
return 1;
|
||||
@@ -3379,7 +3519,7 @@ int SecureMsgValidate(unsigned char *pHeader, unsigned char *pPayload, uint32_t
|
||||
HMAC_CTX *ctx = HMAC_CTX_new();
|
||||
|
||||
unsigned int nBytes;
|
||||
if (!HMAC_Init_ex(ctx, &civ[0], 32, EVP_sha256(), NULL)
|
||||
if (!HMAC_Init_ex(ctx, &civ[0], 32, EVP_sha256(), nullptr)
|
||||
|| !HMAC_Update(ctx, (unsigned char*) pHeader+4, SMSG_HDR_LEN-4)
|
||||
|| !HMAC_Update(ctx, (unsigned char*) pPayload, nPayload)
|
||||
|| !HMAC_Update(ctx, pPayload, nPayload)
|
||||
@@ -3456,7 +3596,7 @@ int SecureMsgSetHash(unsigned char *pHeader, unsigned char *pPayload, uint32_t n
|
||||
memcpy(civ+i, &nonse, 4);
|
||||
|
||||
unsigned int nBytes;
|
||||
if (!HMAC_Init_ex(ctx, &civ[0], 32, EVP_sha256(), NULL)
|
||||
if (!HMAC_Init_ex(ctx, &civ[0], 32, EVP_sha256(), nullptr)
|
||||
|| !HMAC_Update(ctx, (unsigned char*) pHeader+4, SMSG_HDR_LEN-4)
|
||||
|| !HMAC_Update(ctx, (unsigned char*) pPayload, nPayload)
|
||||
|| !HMAC_Update(ctx, pPayload, nPayload)
|
||||
@@ -3539,7 +3679,7 @@ int SecureMsgEncrypt(SecureMessage& smsg, std::string& addressFrom, std::string&
|
||||
3 addressFrom is invalid.
|
||||
4 addressTo is invalid.
|
||||
5 Could not get public key for addressTo.
|
||||
6 ECDH_compute_key failed
|
||||
6 ECDH key derivation failed
|
||||
7 Could not get private key for addressFrom.
|
||||
8 Could not allocate memory.
|
||||
9 Could not compress message data.
|
||||
@@ -3634,22 +3774,26 @@ int SecureMsgEncrypt(SecureMessage& smsg, std::string& addressFrom, std::string&
|
||||
|
||||
std::vector<unsigned char> vchP;
|
||||
vchP.resize(32);
|
||||
EC_KEY* pkeyr = keyR.GetECKey();
|
||||
EC_KEY* pkeyK = keyK.GetECKey();
|
||||
|
||||
// always seems to be 32, worth checking?
|
||||
//int field_size = EC_GROUP_get_degree(EC_KEY_get0_group(pkeyr));
|
||||
//int secret_len = (field_size+7)/8;
|
||||
//printf("secret_len %d.\n", secret_len);
|
||||
|
||||
// -- ECDH_compute_key returns the same P if fed compressed or uncompressed public keys
|
||||
int lenP = ECDH_compute_key(&vchP[0], 32, EC_KEY_get0_public_key(pkeyK), pkeyr, NULL);
|
||||
|
||||
if (lenP != 32)
|
||||
|
||||
bool fCompressedR = false;
|
||||
CSecret secretR = keyR.GetSecret(fCompressedR);
|
||||
if (secretR.size() != 32)
|
||||
{
|
||||
printf("ECDH_compute_key failed, lenP: %d.\n", lenP);
|
||||
printf("ECDH: keyR secret has unexpected size %zu.\n", secretR.size());
|
||||
return 6;
|
||||
};
|
||||
}
|
||||
std::vector<unsigned char> vchPubK = keyK.GetPubKey().Raw();
|
||||
if (vchPubK.size() != 33 && vchPubK.size() != 65)
|
||||
{
|
||||
printf("ECDH: keyK pubkey has unexpected size %zu.\n", vchPubK.size());
|
||||
return 6;
|
||||
}
|
||||
|
||||
if (!ECDH_xonly_secp256k1(&vchP[0], &secretR[0], &vchPubK[0], vchPubK.size()))
|
||||
{
|
||||
printf("ECDH (encrypt): secp256k1_ecdh failed.\n");
|
||||
return 6;
|
||||
}
|
||||
|
||||
CPubKey cpkR = keyR.GetPubKey();
|
||||
if (!cpkR.IsValid()
|
||||
@@ -3777,7 +3921,7 @@ int SecureMsgEncrypt(SecureMessage& smsg, std::string& addressFrom, std::string&
|
||||
unsigned int nBytes = 32;
|
||||
HMAC_CTX *ctx = HMAC_CTX_new();
|
||||
|
||||
if (!HMAC_Init_ex(ctx, &key_m[0], 32, EVP_sha256(), NULL)
|
||||
if (!HMAC_Init_ex(ctx, &key_m[0], 32, EVP_sha256(), nullptr)
|
||||
|| !HMAC_Update(ctx, (unsigned char*) &smsg.timestamp, sizeof(smsg.timestamp))
|
||||
|| !HMAC_Update(ctx, &vchCiphertext[0], vchCiphertext.size())
|
||||
|| !HMAC_Final(ctx, smsg.mac, &nBytes)
|
||||
@@ -3837,7 +3981,7 @@ int SecureMsgSend(std::string& addressFrom, std::string& addressTo, std::string&
|
||||
case 3: sError = "Invalid addressFrom."; break;
|
||||
case 4: sError = "Invalid addressTo."; break;
|
||||
case 5: sError = "Could not get public key for addressTo."; break;
|
||||
case 6: sError = "ECDH_compute_key failed."; break;
|
||||
case 6: sError = "ECDH key derivation failed."; break;
|
||||
case 7: sError = "Could not get private key for addressFrom."; break;
|
||||
case 8: sError = "Could not allocate memory."; break;
|
||||
case 9: sError = "Could not compress message data."; break;
|
||||
@@ -4050,16 +4194,26 @@ int SecureMsgDecrypt(bool fTestOnly, std::string& address, unsigned char *pHeade
|
||||
// -- Do an EC point multiply with private key k and public key R. This gives you public key P.
|
||||
std::vector<unsigned char> vchP;
|
||||
vchP.resize(32);
|
||||
EC_KEY* pkeyk = keyDest.GetECKey();
|
||||
EC_KEY* pkeyR = keyR.GetECKey();
|
||||
|
||||
int lenPdec = ECDH_compute_key(&vchP[0], 32, EC_KEY_get0_public_key(pkeyR), pkeyk, NULL);
|
||||
|
||||
if (lenPdec != 32)
|
||||
|
||||
bool fCompressedDest = false;
|
||||
CSecret secretDest = keyDest.GetSecret(fCompressedDest);
|
||||
if (secretDest.size() != 32)
|
||||
{
|
||||
printf("ECDH_compute_key failed, lenPdec: %d.\n", lenPdec);
|
||||
printf("ECDH: keyDest secret has unexpected size %zu.\n", secretDest.size());
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
std::vector<unsigned char> vchPubR = keyR.GetPubKey().Raw();
|
||||
if (vchPubR.size() != 33 && vchPubR.size() != 65)
|
||||
{
|
||||
printf("ECDH: keyR pubkey has unexpected size %zu.\n", vchPubR.size());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!ECDH_xonly_secp256k1(&vchP[0], &secretDest[0], &vchPubR[0], vchPubR.size()))
|
||||
{
|
||||
printf("ECDH (decrypt): secp256k1_ecdh failed.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// -- Use public key P to calculate the SHA512 hash H.
|
||||
@@ -4077,7 +4231,7 @@ int SecureMsgDecrypt(bool fTestOnly, std::string& address, unsigned char *pHeade
|
||||
unsigned int nBytes = 32;
|
||||
HMAC_CTX *ctx = HMAC_CTX_new();
|
||||
|
||||
if (!HMAC_Init_ex(ctx, &key_m[0], 32, EVP_sha256(), NULL)
|
||||
if (!HMAC_Init_ex(ctx, &key_m[0], 32, EVP_sha256(), nullptr)
|
||||
|| !HMAC_Update(ctx, (unsigned char*) &psmsg->timestamp, sizeof(psmsg->timestamp))
|
||||
|| !HMAC_Update(ctx, pPayload, nPayload)
|
||||
|| !HMAC_Final(ctx, MAC, &nBytes)
|
||||
|
||||
+23
-13
@@ -4,11 +4,15 @@
|
||||
#ifndef SEC_MESSAGE_H
|
||||
#define SEC_MESSAGE_H
|
||||
|
||||
#include <leveldb/db.h>
|
||||
#include <leveldb/write_batch.h>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include <rocksdb/db.h>
|
||||
#include <rocksdb/write_batch.h>
|
||||
|
||||
#include "net.h"
|
||||
#include "db.h"
|
||||
#include "util_signal.h"
|
||||
#include "wallet.h"
|
||||
#include "lz4/lz4.h"
|
||||
|
||||
@@ -41,13 +45,13 @@ extern bool fSecMsgEnabled;
|
||||
class SecMsgStored;
|
||||
|
||||
// Inbox db changed, called with lock cs_smsgDB held.
|
||||
extern boost::signals2::signal<void (SecMsgStored& inboxHdr)> NotifySecMsgInboxChanged;
|
||||
extern CSignal<void(SecMsgStored&)> NotifySecMsgInboxChanged;
|
||||
|
||||
// Outbox db changed, called with lock cs_smsgDB held.
|
||||
extern boost::signals2::signal<void (SecMsgStored& outboxHdr)> NotifySecMsgOutboxChanged;
|
||||
extern CSignal<void(SecMsgStored&)> NotifySecMsgOutboxChanged;
|
||||
|
||||
// Wallet Unlocked, called after all messages received while locked have been processed.
|
||||
extern boost::signals2::signal<void ()> NotifySecMsgWalletUnlocked;
|
||||
extern CSignal<void()> NotifySecMsgWalletUnlocked;
|
||||
|
||||
|
||||
class SecMsgBucket;
|
||||
@@ -70,14 +74,14 @@ public:
|
||||
SecureMessage()
|
||||
{
|
||||
nPayload = 0;
|
||||
pPayload = NULL;
|
||||
pPayload = nullptr;
|
||||
};
|
||||
|
||||
~SecureMessage()
|
||||
{
|
||||
if (pPayload)
|
||||
delete[] pPayload;
|
||||
pPayload = NULL;
|
||||
pPayload = nullptr;
|
||||
};
|
||||
|
||||
unsigned char hash[4];
|
||||
@@ -290,7 +294,7 @@ class SecMsgDB
|
||||
public:
|
||||
SecMsgDB()
|
||||
{
|
||||
activeBatch = NULL;
|
||||
activeBatch = nullptr;
|
||||
};
|
||||
|
||||
~SecMsgDB()
|
||||
@@ -313,16 +317,22 @@ public:
|
||||
bool WritePK(CKeyID& addr, CPubKey& pubkey);
|
||||
bool ExistsPK(CKeyID& addr);
|
||||
|
||||
bool NextSmesg(leveldb::Iterator* it, std::string& prefix, unsigned char* vchKey, SecMsgStored& smsgStored);
|
||||
bool NextSmesgKey(leveldb::Iterator* it, std::string& prefix, unsigned char* vchKey);
|
||||
bool NextSmesg(rocksdb::Iterator* it, std::string& prefix, unsigned char* vchKey, SecMsgStored& smsgStored);
|
||||
bool NextSmesgKey(rocksdb::Iterator* it, std::string& prefix, unsigned char* vchKey);
|
||||
bool ReadSmesg(unsigned char* chKey, SecMsgStored& smsgStored);
|
||||
bool WriteSmesg(unsigned char* chKey, SecMsgStored& smsgStored);
|
||||
bool ExistsSmesg(unsigned char* chKey);
|
||||
bool EraseSmesg(unsigned char* chKey);
|
||||
|
||||
leveldb::DB *pdb; // points to the global instance
|
||||
leveldb::WriteBatch *activeBatch;
|
||||
|
||||
rocksdb::DB *pdb; // points to the global instance
|
||||
rocksdb::WriteBatch *activeBatch;
|
||||
|
||||
// Parallel record of every pending write (value) or delete (nullopt) on
|
||||
// activeBatch. Used by ScanBatch to answer "is this key in the active
|
||||
// batch?" without iterating the WriteBatch via Handler — Ubuntu's
|
||||
// librocksdb-dev hides typeinfo for rocksdb::WriteBatch::Handler so a
|
||||
// subclass-based scan fails to link there.
|
||||
std::map<std::string, std::optional<std::string>> pendingBatch;
|
||||
};
|
||||
|
||||
std::string getTimeString(int64_t timestamp, char *buffer, size_t nBuffer);
|
||||
|
||||
@@ -0,0 +1,644 @@
|
||||
// Copyright (c) 2026 Triangles developers
|
||||
// Distributed under the MIT/X11 software license
|
||||
|
||||
#include "snapshotnet.h"
|
||||
|
||||
#include "checkpoints.h"
|
||||
#include "main.h"
|
||||
#include "net.h"
|
||||
#include "protocol.h"
|
||||
#include "sync.h"
|
||||
#include "ui_interface.h"
|
||||
#include "util.h"
|
||||
#include "utxosnapshot.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
extern std::vector<CNode*> vNodes;
|
||||
extern CCriticalSection cs_vNodes;
|
||||
extern uint64_t nLocalServices;
|
||||
|
||||
namespace SnapshotNet {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fetcher state
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
|
||||
struct ChunkRequest
|
||||
{
|
||||
int64_t offset;
|
||||
int32_t size;
|
||||
int64_t requestedAt; // GetTimeMicros() when sent
|
||||
CNode* pnode; // not refcounted; checked under cs_vNodes
|
||||
bool done;
|
||||
};
|
||||
|
||||
struct FetcherState
|
||||
{
|
||||
std::mutex mu;
|
||||
std::condition_variable cv;
|
||||
bool active = false;
|
||||
bool finished = false;
|
||||
bool success = false;
|
||||
|
||||
int targetHeight = 0;
|
||||
uint256 expectedFileHash;
|
||||
int64_t totalSize = 0;
|
||||
|
||||
// Per-peer announcement: peer NodeId -> AvailableSnapshot for our targetHeight
|
||||
std::map<int, AvailableSnapshot> peerOffers;
|
||||
|
||||
// Outstanding chunk requests, keyed by chunk-aligned offset.
|
||||
std::map<int64_t, ChunkRequest> pending;
|
||||
|
||||
// Bitmap of chunks already written, by chunk-aligned offset.
|
||||
std::map<int64_t, bool> received;
|
||||
|
||||
fs::path destPath;
|
||||
FILE* fpDest = nullptr;
|
||||
};
|
||||
|
||||
static FetcherState g_fetch;
|
||||
|
||||
// Per-CNode integer id (used as map key). We stash a counter via the node's
|
||||
// pointer address — the pointer itself is stable for the node's lifetime, but
|
||||
// reused across reconnects, so we just use it as an opaque identity for the
|
||||
// duration of a single fetch.
|
||||
static intptr_t NodeKey(const CNode* p) { return reinterpret_cast<intptr_t>(p); }
|
||||
|
||||
static int64_t AlignDown(int64_t off, int32_t chunk)
|
||||
{
|
||||
return (off / chunk) * chunk;
|
||||
}
|
||||
|
||||
static void CloseDest()
|
||||
{
|
||||
if (g_fetch.fpDest) {
|
||||
fclose(g_fetch.fpDest);
|
||||
g_fetch.fpDest = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void ResetState()
|
||||
{
|
||||
g_fetch.active = false;
|
||||
g_fetch.finished = false;
|
||||
g_fetch.success = false;
|
||||
g_fetch.targetHeight = 0;
|
||||
g_fetch.expectedFileHash = 0;
|
||||
g_fetch.totalSize = 0;
|
||||
g_fetch.peerOffers.clear();
|
||||
g_fetch.pending.clear();
|
||||
g_fetch.received.clear();
|
||||
CloseDest();
|
||||
g_fetch.destPath.clear();
|
||||
}
|
||||
|
||||
// Verify the full destination file's SHA256 matches g_fetch.expectedFileHash.
|
||||
// Returns true on match. Caller holds g_fetch.mu.
|
||||
static bool VerifyDestFileHash(std::string& strErr)
|
||||
{
|
||||
if (!g_fetch.fpDest) {
|
||||
strErr = "no dest file open";
|
||||
return false;
|
||||
}
|
||||
fflush(g_fetch.fpDest);
|
||||
fseek(g_fetch.fpDest, 0, SEEK_SET);
|
||||
|
||||
SHA256_CTX ctx;
|
||||
SHA256_Init(&ctx);
|
||||
|
||||
std::vector<unsigned char> buf(64 * 1024);
|
||||
int64_t total = 0;
|
||||
while (true) {
|
||||
size_t n = fread(buf.data(), 1, buf.size(), g_fetch.fpDest);
|
||||
if (n == 0) break;
|
||||
SHA256_Update(&ctx, buf.data(), n);
|
||||
total += (int64_t)n;
|
||||
}
|
||||
if (total != g_fetch.totalSize) {
|
||||
strErr = strprintf("size mismatch: have %" PRId64 " want %" PRId64, total, g_fetch.totalSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 actual;
|
||||
SHA256_Final((unsigned char*)&actual, &ctx);
|
||||
if (actual != g_fetch.expectedFileHash) {
|
||||
strErr = "snapshot file hash mismatch";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Build the list of chunk offsets that still need a request (not pending, not done).
|
||||
// Caller holds g_fetch.mu.
|
||||
static std::vector<int64_t> MissingChunkOffsets()
|
||||
{
|
||||
std::vector<int64_t> out;
|
||||
if (g_fetch.totalSize <= 0) return out;
|
||||
for (int64_t off = 0; off < g_fetch.totalSize; off += SNAPSHOT_CHUNK_MAX) {
|
||||
if (g_fetch.received.count(off)) continue;
|
||||
if (g_fetch.pending.count(off)) continue;
|
||||
out.push_back(off);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Send getsnapchunk requests striped across snapshot-capable peers.
|
||||
// Caller holds g_fetch.mu.
|
||||
static int DispatchChunkRequests()
|
||||
{
|
||||
if (!g_fetch.active || g_fetch.finished) return 0;
|
||||
|
||||
std::vector<CNode*> servers;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* p : vNodes) {
|
||||
if (!p->fSuccessfullyConnected) continue;
|
||||
if (p->nVersion < SNAPSHOT_PROTO_VERSION) continue;
|
||||
if (!(p->nServices & NODE_SNAPSHOT)) continue;
|
||||
// peer must have offered our target snapshot
|
||||
auto it = g_fetch.peerOffers.find((int)NodeKey(p));
|
||||
if (it == g_fetch.peerOffers.end()) continue;
|
||||
if (it->second.fileHash != g_fetch.expectedFileHash) continue;
|
||||
servers.push_back(p);
|
||||
}
|
||||
}
|
||||
if (servers.empty()) return 0;
|
||||
|
||||
std::vector<int64_t> missing = MissingChunkOffsets();
|
||||
if (missing.empty()) return 0;
|
||||
|
||||
// Cap inflight to avoid swamping peer send queues. Each chunk is up to
|
||||
// 256 KB; 32 outstanding * 256 KB = 8 MB pipeline per peer max.
|
||||
const size_t kMaxInflightPerPeer = 32;
|
||||
std::map<int, size_t> inflightPerPeer;
|
||||
for (const auto& kv : g_fetch.pending)
|
||||
inflightPerPeer[(int)NodeKey(kv.second.pnode)]++;
|
||||
|
||||
int64_t now = GetTimeMicros();
|
||||
int sent = 0;
|
||||
size_t serverIdx = 0;
|
||||
for (int64_t off : missing) {
|
||||
// Round-robin pick a server with capacity.
|
||||
CNode* pick = nullptr;
|
||||
for (size_t tries = 0; tries < servers.size(); ++tries) {
|
||||
CNode* candidate = servers[(serverIdx + tries) % servers.size()];
|
||||
if (inflightPerPeer[(int)NodeKey(candidate)] < kMaxInflightPerPeer) {
|
||||
pick = candidate;
|
||||
serverIdx = (serverIdx + tries + 1) % servers.size();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pick) break; // all peers saturated; loop will resume later
|
||||
|
||||
int32_t reqSize = (int32_t)std::min<int64_t>(SNAPSHOT_CHUNK_MAX,
|
||||
g_fetch.totalSize - off);
|
||||
ChunkRequest req;
|
||||
req.offset = off;
|
||||
req.size = reqSize;
|
||||
req.requestedAt = now;
|
||||
req.pnode = pick;
|
||||
req.done = false;
|
||||
g_fetch.pending[off] = req;
|
||||
inflightPerPeer[(int)NodeKey(pick)]++;
|
||||
|
||||
// PushMessage is thread-safe (acquires its own cs_vSend).
|
||||
pick->PushMessage("getsnapchunk", g_fetch.targetHeight, off, reqSize);
|
||||
++sent;
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
// Reassign chunks whose request has timed out (peer slow or dropped).
|
||||
// Caller holds g_fetch.mu.
|
||||
static void ReissueStalledChunks(int64_t timeoutMicros)
|
||||
{
|
||||
int64_t now = GetTimeMicros();
|
||||
std::vector<int64_t> stale;
|
||||
for (const auto& kv : g_fetch.pending) {
|
||||
if (now - kv.second.requestedAt > timeoutMicros)
|
||||
stale.push_back(kv.first);
|
||||
}
|
||||
for (int64_t off : stale)
|
||||
g_fetch.pending.erase(off);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public: TryFetchSnapshot
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool TryFetchSnapshot(const fs::path& dataDir, int timeoutSec, std::string& strError)
|
||||
{
|
||||
int snapHeight = Checkpoints::GetBestSnapshotHeight();
|
||||
if (snapHeight <= 0) {
|
||||
strError = "no compiled-in snapshot hash available";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 expectedHash;
|
||||
if (!Checkpoints::GetSnapshotHash(snapHeight, expectedHash)) {
|
||||
strError = "snapshot hash lookup failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
fs::path destPath = dataDir / "utxo-snapshot.bin";
|
||||
if (fs::exists(destPath)) {
|
||||
// Caller already has a snapshot file; let normal init pick it up.
|
||||
return true;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_fetch.mu);
|
||||
if (g_fetch.active) {
|
||||
strError = "snapshot fetch already in progress";
|
||||
return false;
|
||||
}
|
||||
ResetState();
|
||||
g_fetch.targetHeight = snapHeight;
|
||||
g_fetch.expectedFileHash = expectedHash;
|
||||
g_fetch.destPath = destPath;
|
||||
g_fetch.active = true;
|
||||
}
|
||||
|
||||
printf("SnapshotNet: requesting snapshot at height %d (hash=%s)\n",
|
||||
snapHeight, expectedHash.ToString().c_str());
|
||||
uiInterface.InitMessage(_("Looking for UTXO snapshot peers..."));
|
||||
|
||||
int64_t start = GetTime();
|
||||
int64_t deadline = start + timeoutSec;
|
||||
int64_t lastBroadcast = 0;
|
||||
int64_t lastProgress = 0;
|
||||
|
||||
while (GetTime() < deadline) {
|
||||
// (Re)broadcast getsnap every 30s to pick up newly connected peers.
|
||||
if (GetTime() - lastBroadcast >= 30) {
|
||||
int peerCount = 0;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* p : vNodes) {
|
||||
if (!p->fSuccessfullyConnected) continue;
|
||||
if (p->nVersion < SNAPSHOT_PROTO_VERSION) continue;
|
||||
if (!(p->nServices & NODE_SNAPSHOT)) continue;
|
||||
p->PushMessage("getsnap");
|
||||
++peerCount;
|
||||
}
|
||||
}
|
||||
lastBroadcast = GetTime();
|
||||
printf("SnapshotNet: getsnap sent to %d snapshot-capable peers\n", peerCount);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_fetch.mu);
|
||||
|
||||
// If we have at least one matching offer and total size known,
|
||||
// open dest file and start dispatching chunk requests.
|
||||
if (g_fetch.totalSize > 0 && !g_fetch.fpDest) {
|
||||
g_fetch.fpDest = fopen(g_fetch.destPath.string().c_str(), "wb+");
|
||||
if (!g_fetch.fpDest) {
|
||||
strError = "cannot create " + g_fetch.destPath.string();
|
||||
g_fetch.finished = true;
|
||||
g_fetch.success = false;
|
||||
break;
|
||||
}
|
||||
// Pre-size the file so chunk writes can use random access.
|
||||
if (fseek(g_fetch.fpDest, g_fetch.totalSize - 1, SEEK_SET) == 0) {
|
||||
char zero = 0;
|
||||
fwrite(&zero, 1, 1, g_fetch.fpDest);
|
||||
fflush(g_fetch.fpDest);
|
||||
}
|
||||
}
|
||||
|
||||
ReissueStalledChunks(45 * (int64_t)1000000); // 45s per-chunk timeout
|
||||
DispatchChunkRequests();
|
||||
|
||||
// Progress print every 10s
|
||||
if (GetTime() - lastProgress >= 10 && g_fetch.totalSize > 0) {
|
||||
int64_t got = (int64_t)g_fetch.received.size() * SNAPSHOT_CHUNK_MAX;
|
||||
if (got > g_fetch.totalSize) got = g_fetch.totalSize;
|
||||
printf("SnapshotNet: %" PRId64 " / %" PRId64 " bytes (%" PRId64 "%%)\n",
|
||||
got, g_fetch.totalSize,
|
||||
(int64_t)((got * 100) / g_fetch.totalSize));
|
||||
lastProgress = GetTime();
|
||||
}
|
||||
|
||||
// All chunks in?
|
||||
if (g_fetch.totalSize > 0) {
|
||||
int64_t total = (g_fetch.totalSize + SNAPSHOT_CHUNK_MAX - 1) / SNAPSHOT_CHUNK_MAX;
|
||||
if ((int64_t)g_fetch.received.size() >= total) {
|
||||
std::string verifyErr;
|
||||
if (VerifyDestFileHash(verifyErr)) {
|
||||
g_fetch.success = true;
|
||||
} else {
|
||||
strError = verifyErr;
|
||||
g_fetch.success = false;
|
||||
// Drop bad file so we don't trick later loaders.
|
||||
CloseDest();
|
||||
std::error_code ec;
|
||||
fs::remove(g_fetch.destPath, ec);
|
||||
}
|
||||
g_fetch.finished = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
|
||||
bool ok;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_fetch.mu);
|
||||
if (!g_fetch.finished) {
|
||||
// Timed out
|
||||
if (strError.empty())
|
||||
strError = strprintf("timeout after %d seconds (totalSize=%" PRId64 ", chunks=%" PRIszu ")",
|
||||
timeoutSec, g_fetch.totalSize, g_fetch.received.size());
|
||||
CloseDest();
|
||||
std::error_code ec;
|
||||
fs::remove(g_fetch.destPath, ec);
|
||||
}
|
||||
ok = g_fetch.success;
|
||||
ResetState();
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
printf("SnapshotNet: snapshot fetched and verified (%s)\n",
|
||||
destPath.string().c_str());
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Server side: read from local snapshot file
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
|
||||
// Cached metadata for the local snapshot file. Filled lazily by EnsureLocalSnapshot
|
||||
// or by HasServableSnapshot scanning the dest path.
|
||||
static std::mutex g_localMu;
|
||||
static bool g_localScanned = false;
|
||||
static bool g_localPresent = false;
|
||||
static int g_localHeight = 0;
|
||||
static uint256 g_localFileHash = 0;
|
||||
static int64_t g_localTotalSize = 0;
|
||||
static fs::path g_localPath;
|
||||
|
||||
static bool ScanLocalSnapshot()
|
||||
{
|
||||
g_localPresent = false;
|
||||
g_localHeight = 0;
|
||||
g_localFileHash = 0;
|
||||
g_localTotalSize = 0;
|
||||
g_localPath = GetDataDir() / "utxo-snapshot.bin";
|
||||
|
||||
if (!fs::exists(g_localPath)) return false;
|
||||
|
||||
int snapHeight = Checkpoints::GetBestSnapshotHeight();
|
||||
if (snapHeight <= 0) return false;
|
||||
|
||||
uint256 expectedHash;
|
||||
if (!Checkpoints::GetSnapshotHash(snapHeight, expectedHash)) return false;
|
||||
|
||||
std::error_code ec;
|
||||
int64_t sz = (int64_t)fs::file_size(g_localPath, ec);
|
||||
if (ec) return false;
|
||||
|
||||
// Hash the file once on first scan to confirm it matches the compiled-in
|
||||
// snapshot hash. A node won't advertise NODE_SNAPSHOT if the local file is
|
||||
// corrupt or for a different height.
|
||||
FILE* f = fopen(g_localPath.string().c_str(), "rb");
|
||||
if (!f) return false;
|
||||
|
||||
SHA256_CTX ctx;
|
||||
SHA256_Init(&ctx);
|
||||
std::vector<unsigned char> buf(64 * 1024);
|
||||
while (true) {
|
||||
size_t n = fread(buf.data(), 1, buf.size(), f);
|
||||
if (n == 0) break;
|
||||
SHA256_Update(&ctx, buf.data(), n);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
uint256 actual;
|
||||
SHA256_Final((unsigned char*)&actual, &ctx);
|
||||
if (actual != expectedHash) {
|
||||
printf("SnapshotNet: local utxo-snapshot.bin hash mismatch — not advertising\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_localPresent = true;
|
||||
g_localHeight = snapHeight;
|
||||
g_localFileHash = expectedHash;
|
||||
g_localTotalSize = sz;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ReadLocalChunk(int64_t offset, int32_t size, std::vector<unsigned char>& out)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_localMu);
|
||||
if (!g_localPresent) return false;
|
||||
if (offset < 0 || offset >= g_localTotalSize) return false;
|
||||
if (size <= 0 || size > SNAPSHOT_CHUNK_MAX) return false;
|
||||
int32_t actual = (int32_t)std::min<int64_t>(size, g_localTotalSize - offset);
|
||||
|
||||
FILE* f = fopen(g_localPath.string().c_str(), "rb");
|
||||
if (!f) return false;
|
||||
if (fseek(f, offset, SEEK_SET) != 0) { fclose(f); return false; }
|
||||
|
||||
out.resize(actual);
|
||||
size_t n = fread(out.data(), 1, actual, f);
|
||||
fclose(f);
|
||||
if ((int32_t)n != actual) { out.clear(); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool HasServableSnapshot()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_localMu);
|
||||
if (!g_localScanned) {
|
||||
ScanLocalSnapshot();
|
||||
g_localScanned = true;
|
||||
}
|
||||
return g_localPresent;
|
||||
}
|
||||
|
||||
void EnsureLocalSnapshot()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_localMu);
|
||||
if (g_localScanned && g_localPresent) return;
|
||||
}
|
||||
|
||||
int snapHeight = Checkpoints::GetBestSnapshotHeight();
|
||||
if (snapHeight <= 0) return;
|
||||
|
||||
fs::path destPath = GetDataDir() / "utxo-snapshot.bin";
|
||||
|
||||
// If the file exists, scan it (validates hash). Otherwise, generate it
|
||||
// from the current chain if our tip is past the snapshot height.
|
||||
bool needGenerate = !fs::exists(destPath);
|
||||
|
||||
if (needGenerate) {
|
||||
if (nBestHeight < snapHeight) return; // not synced past it yet
|
||||
printf("SnapshotNet: dumping local snapshot at height %d -> %s\n",
|
||||
snapHeight, destPath.string().c_str());
|
||||
std::string err;
|
||||
// DumpSnapshot dumps from current chain tip — only call when tip == snapHeight,
|
||||
// otherwise the produced file won't match the published hash. Skip for now;
|
||||
// operators must produce the canonical file out-of-band and place it here.
|
||||
// (Auto-dump from arbitrary tip would not produce the canonical hash.)
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_localMu);
|
||||
ScanLocalSnapshot();
|
||||
g_localScanned = true;
|
||||
}
|
||||
|
||||
if (g_localPresent) {
|
||||
nLocalServices |= NODE_SNAPSHOT;
|
||||
printf("SnapshotNet: serving local snapshot height=%d size=%" PRId64 "\n",
|
||||
g_localHeight, g_localTotalSize);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Server side: P2P message dispatch
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool ProcessSnapshotMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv)
|
||||
{
|
||||
if (strCommand == "getsnap")
|
||||
{
|
||||
// Reply with a list of snapshots we can serve. Currently only the
|
||||
// single canonical snapshot at the latest checkpoint with a published
|
||||
// hash; future versions may serve multiple.
|
||||
std::vector<AvailableSnapshot> reply;
|
||||
if (HasServableSnapshot()) {
|
||||
std::lock_guard<std::mutex> lk(g_localMu);
|
||||
AvailableSnapshot a;
|
||||
a.height = g_localHeight;
|
||||
a.fileHash = g_localFileHash;
|
||||
a.totalSize = g_localTotalSize;
|
||||
reply.push_back(a);
|
||||
}
|
||||
pfrom->PushMessage("snap", reply);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strCommand == "snap")
|
||||
{
|
||||
std::vector<AvailableSnapshot> offers;
|
||||
vRecv >> offers;
|
||||
if (offers.size() > 16) {
|
||||
pfrom->Misbehaving(20);
|
||||
return true;
|
||||
}
|
||||
std::lock_guard<std::mutex> lk(g_fetch.mu);
|
||||
if (!g_fetch.active) return true;
|
||||
for (const AvailableSnapshot& a : offers) {
|
||||
if (a.height != g_fetch.targetHeight) continue;
|
||||
if (a.fileHash != g_fetch.expectedFileHash) continue;
|
||||
if (a.totalSize <= 0 || a.totalSize > (int64_t)4 * 1024 * 1024 * 1024) continue;
|
||||
g_fetch.peerOffers[(int)NodeKey(pfrom)] = a;
|
||||
if (g_fetch.totalSize == 0)
|
||||
g_fetch.totalSize = a.totalSize;
|
||||
}
|
||||
g_fetch.cv.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strCommand == "getsnapchunk")
|
||||
{
|
||||
int height;
|
||||
int64_t offset;
|
||||
int32_t size;
|
||||
vRecv >> height >> offset >> size;
|
||||
|
||||
std::vector<unsigned char> data;
|
||||
if (HasServableSnapshot()) {
|
||||
std::lock_guard<std::mutex> lk(g_localMu);
|
||||
if (height == g_localHeight)
|
||||
ReadLocalChunk(offset, size, data);
|
||||
}
|
||||
// Always reply, even with empty data, so the requester can give up
|
||||
// on this peer for this chunk and reissue elsewhere.
|
||||
pfrom->PushMessage("snapchunk", height, offset, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strCommand == "snapchunk")
|
||||
{
|
||||
int height;
|
||||
int64_t offset;
|
||||
std::vector<unsigned char> data;
|
||||
vRecv >> height >> offset >> data;
|
||||
|
||||
if (data.size() > (size_t)SNAPSHOT_CHUNK_MAX) {
|
||||
pfrom->Misbehaving(20);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lk(g_fetch.mu);
|
||||
if (!g_fetch.active) return true;
|
||||
if (height != g_fetch.targetHeight) return true;
|
||||
if (data.empty()) {
|
||||
// Peer doesn't have it; drop pending so it gets reissued.
|
||||
g_fetch.pending.erase(offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (offset < 0 || offset >= g_fetch.totalSize) {
|
||||
pfrom->Misbehaving(10);
|
||||
g_fetch.pending.erase(offset);
|
||||
return true;
|
||||
}
|
||||
int32_t expected = (int32_t)std::min<int64_t>(SNAPSHOT_CHUNK_MAX,
|
||||
g_fetch.totalSize - offset);
|
||||
if ((int32_t)data.size() != expected) {
|
||||
pfrom->Misbehaving(10);
|
||||
g_fetch.pending.erase(offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (g_fetch.fpDest) {
|
||||
if (fseek(g_fetch.fpDest, offset, SEEK_SET) == 0) {
|
||||
size_t w = fwrite(data.data(), 1, data.size(), g_fetch.fpDest);
|
||||
if (w == data.size()) {
|
||||
g_fetch.received[offset] = true;
|
||||
g_fetch.pending.erase(offset);
|
||||
g_fetch.cv.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace SnapshotNet
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2026 Triangles developers
|
||||
// Distributed under the MIT/X11 software license
|
||||
|
||||
#ifndef TRIANGLES_SNAPSHOTNET_H
|
||||
#define TRIANGLES_SNAPSHOTNET_H
|
||||
|
||||
#include "uint256.h"
|
||||
#include "serialize.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class CNode;
|
||||
class CDataStream;
|
||||
|
||||
namespace SnapshotNet {
|
||||
|
||||
// Maximum bytes returned per snapshot chunk reply. Sized for Tor cell efficiency
|
||||
// (Tor sends 514-byte cells; ~256 KB amortizes overhead without exceeding the
|
||||
// 32 MB peer send buffer when many chunks are queued).
|
||||
static const int32_t SNAPSHOT_CHUNK_MAX = 256 * 1024;
|
||||
|
||||
// One advertised snapshot a peer can serve.
|
||||
struct AvailableSnapshot
|
||||
{
|
||||
int height;
|
||||
uint256 fileHash;
|
||||
int64_t totalSize;
|
||||
|
||||
AvailableSnapshot() : height(0), fileHash(0), totalSize(0) {}
|
||||
|
||||
IMPLEMENT_SERIALIZE
|
||||
(
|
||||
READWRITE(height);
|
||||
READWRITE(fileHash);
|
||||
READWRITE(totalSize);
|
||||
)
|
||||
};
|
||||
|
||||
// Initial snapshot fetch on a fresh install.
|
||||
// - Picks the latest checkpoint height with a published snapshot hash.
|
||||
// - Polls connected peers for matching snapshots.
|
||||
// - Stripes chunk requests across peers in parallel.
|
||||
// - Verifies the full file SHA256 against the compiled-in snapshot hash.
|
||||
// - Writes the result to dataDir/utxo-snapshot.bin.
|
||||
//
|
||||
// Blocks for up to timeoutSec waiting for peers + transfer. Returns true if a
|
||||
// verified snapshot was written, false on timeout/no peer/verification fail.
|
||||
bool TryFetchSnapshot(const std::filesystem::path& dataDir,
|
||||
int timeoutSec,
|
||||
std::string& strError);
|
||||
|
||||
// Server-side message dispatch. Called from main.cpp ProcessMessage.
|
||||
// Returns true if strCommand was a snapshot-protocol message (handled or
|
||||
// rejected for malformed input).
|
||||
bool ProcessSnapshotMessage(CNode* pfrom,
|
||||
const std::string& strCommand,
|
||||
CDataStream& vRecv);
|
||||
|
||||
// Generate dataDir/utxo-snapshot.bin from the current chain if our tip is past
|
||||
// the latest checkpoint height with a published snapshot hash and the file does
|
||||
// not already exist. Safe to call repeatedly; no-op when conditions aren't met.
|
||||
// Sets the NODE_SNAPSHOT service flag on success.
|
||||
void EnsureLocalSnapshot();
|
||||
|
||||
// Returns true when this node holds a verified snapshot file ready to serve.
|
||||
bool HasServableSnapshot();
|
||||
|
||||
} // namespace SnapshotNet
|
||||
|
||||
#endif // TRIANGLES_SNAPSHOTNET_H
|
||||
+7
-7
@@ -46,11 +46,11 @@ private:
|
||||
int sourceLine;
|
||||
};
|
||||
|
||||
typedef std::vector< std::pair<void*, CLockLocation> > LockStack;
|
||||
using LockStack = std::vector<std::pair<void*, CLockLocation>>;
|
||||
|
||||
static boost::mutex dd_mutex;
|
||||
static std::mutex dd_mutex;
|
||||
static std::map<std::pair<void*, void*>, LockStack> lockorders;
|
||||
static boost::thread_specific_ptr<LockStack> lockstack;
|
||||
static thread_local std::unique_ptr<LockStack> lockstack;
|
||||
|
||||
|
||||
static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch, const LockStack& s1, const LockStack& s2)
|
||||
@@ -74,24 +74,24 @@ static void potential_deadlock_detected(const std::pair<void*, void*>& mismatch,
|
||||
|
||||
static void push_lock(void* c, const CLockLocation& locklocation, bool fTry)
|
||||
{
|
||||
if (lockstack.get() == NULL)
|
||||
if (!lockstack)
|
||||
lockstack.reset(new LockStack);
|
||||
|
||||
if (fDebug) printf("Locking: %s\n", locklocation.ToString().c_str());
|
||||
dd_mutex.lock();
|
||||
|
||||
(*lockstack).push_back(std::make_pair(c, locklocation));
|
||||
(*lockstack).push_back({c, locklocation});
|
||||
|
||||
if (!fTry) {
|
||||
for (const auto& i : (*lockstack)) {
|
||||
if (i.first == c) break;
|
||||
|
||||
std::pair<void*, void*> p1 = std::make_pair(i.first, c);
|
||||
std::pair<void*, void*> p1 = {i.first, c};
|
||||
if (lockorders.count(p1))
|
||||
continue;
|
||||
lockorders[p1] = (*lockstack);
|
||||
|
||||
std::pair<void*, void*> p2 = std::make_pair(c, i.first);
|
||||
std::pair<void*, void*> p2 = {c, i.first};
|
||||
if (lockorders.count(p2))
|
||||
{
|
||||
potential_deadlock_detected(p1, lockorders[p2], lockorders[p1]);
|
||||
|
||||
+17
-22
@@ -5,19 +5,14 @@
|
||||
#ifndef TRIANGLES_SYNC_H
|
||||
#define TRIANGLES_SYNC_H
|
||||
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/recursive_mutex.hpp>
|
||||
#include <boost/thread/locks.hpp>
|
||||
#include <boost/thread/condition_variable.hpp>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
/** Recursive mutex: supports recursive locking, but no waiting */
|
||||
using CCriticalSection = std::recursive_mutex;
|
||||
|
||||
|
||||
|
||||
/** Wrapped boost mutex: supports recursive locking, but no waiting */
|
||||
typedef boost::recursive_mutex CCriticalSection;
|
||||
|
||||
/** Wrapped boost mutex: supports waiting but not recursive locking */
|
||||
typedef boost::mutex CWaitableCriticalSection;
|
||||
/** Plain mutex: supports waiting but not recursive locking */
|
||||
using CWaitableCriticalSection = std::mutex;
|
||||
|
||||
#ifdef DEBUG_LOCKORDER
|
||||
void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false);
|
||||
@@ -31,12 +26,12 @@ void static inline LeaveCritical() {}
|
||||
void PrintLockContention(const char* pszName, const char* pszFile, int nLine);
|
||||
#endif
|
||||
|
||||
/** Wrapper around boost::unique_lock<Mutex> */
|
||||
/** Wrapper around std::unique_lock<Mutex> */
|
||||
template<typename Mutex>
|
||||
class CMutexLock
|
||||
{
|
||||
private:
|
||||
boost::unique_lock<Mutex> lock;
|
||||
std::unique_lock<Mutex> lock;
|
||||
public:
|
||||
|
||||
void Enter(const char* pszName, const char* pszFile, int nLine)
|
||||
@@ -77,7 +72,7 @@ public:
|
||||
return lock.owns_lock();
|
||||
}
|
||||
|
||||
CMutexLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) : lock(mutexIn, boost::defer_lock)
|
||||
CMutexLock(Mutex& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) : lock(mutexIn, std::defer_lock)
|
||||
{
|
||||
if (fTry)
|
||||
TryEnter(pszName, pszFile, nLine);
|
||||
@@ -96,7 +91,7 @@ public:
|
||||
return lock.owns_lock();
|
||||
}
|
||||
|
||||
boost::unique_lock<Mutex> &GetLock()
|
||||
std::unique_lock<Mutex> &GetLock()
|
||||
{
|
||||
return lock;
|
||||
}
|
||||
@@ -123,15 +118,15 @@ typedef CMutexLock<CCriticalSection> CCriticalBlock;
|
||||
class CSemaphore
|
||||
{
|
||||
private:
|
||||
boost::condition_variable condition;
|
||||
boost::mutex mutex;
|
||||
std::condition_variable condition;
|
||||
std::mutex mutex;
|
||||
int value;
|
||||
|
||||
public:
|
||||
CSemaphore(int init) : value(init) {}
|
||||
|
||||
void wait() {
|
||||
boost::unique_lock<boost::mutex> lock(mutex);
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
while (value < 1) {
|
||||
condition.wait(lock);
|
||||
}
|
||||
@@ -139,7 +134,7 @@ public:
|
||||
}
|
||||
|
||||
bool try_wait() {
|
||||
boost::unique_lock<boost::mutex> lock(mutex);
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
if (value < 1)
|
||||
return false;
|
||||
value--;
|
||||
@@ -148,7 +143,7 @@ public:
|
||||
|
||||
void post() {
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(mutex);
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
value++;
|
||||
}
|
||||
condition.notify_one();
|
||||
@@ -187,11 +182,11 @@ public:
|
||||
grant.Release();
|
||||
grant.sem = sem;
|
||||
grant.fHaveGrant = fHaveGrant;
|
||||
sem = NULL;
|
||||
sem = nullptr;
|
||||
fHaveGrant = false;
|
||||
}
|
||||
|
||||
CSemaphoreGrant() : sem(NULL), fHaveGrant(false) {}
|
||||
CSemaphoreGrant() : sem(nullptr), fHaveGrant(false) {}
|
||||
|
||||
CSemaphoreGrant(CSemaphore &sema, bool fTry = false) : sem(&sema), fHaveGrant(false) {
|
||||
if (fTry)
|
||||
|
||||
@@ -0,0 +1,664 @@
|
||||
// Copyright (c) 2026 The Triangles developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "syncmanager.h"
|
||||
|
||||
#include "bignum.h"
|
||||
#include "checkpoints.h"
|
||||
#include "main.h"
|
||||
#include "net.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
struct CSyncManager::HeaderNode
|
||||
{
|
||||
CBlock header;
|
||||
int nHeight;
|
||||
uint256 nChainTrust;
|
||||
bool fRequested;
|
||||
int64_t nLastRequestTime;
|
||||
int64_t nFirstRequestTime;
|
||||
int64_t nInsertTime;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
static const unsigned int MAX_HEADER_SYNC_CACHE = 15000;
|
||||
static const size_t HEADER_REDUNDANT_PEER_THRESHOLD = 4;
|
||||
static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 60 * 1000000;
|
||||
static const int64_t HEADER_REDUNDANT_REQUEST_MICROS = 5 * 1000000;
|
||||
static const int64_t HEADER_SYNC_TTL_MICROS = 15 * 60 * 1000000;
|
||||
|
||||
std::map<uint256, CSyncManager::HeaderNode> mapHeaders;
|
||||
uint256 hashBestHeader = 0;
|
||||
int64_t nLastNewHeaderTime = 0;
|
||||
}
|
||||
|
||||
CSyncManager g_syncManager;
|
||||
|
||||
bool CSyncManager::HaveHeader(const uint256& hash) const
|
||||
{
|
||||
return mapHeaders.count(hash) != 0;
|
||||
}
|
||||
|
||||
uint256 CSyncManager::GetBestHeader() const
|
||||
{
|
||||
return hashBestHeader;
|
||||
}
|
||||
|
||||
std::size_t CSyncManager::GetHeaderCount() const
|
||||
{
|
||||
return mapHeaders.size();
|
||||
}
|
||||
|
||||
uint256 CSyncManager::GetHeaderTrust(unsigned int nBits) const
|
||||
{
|
||||
CBigNum bnTarget;
|
||||
bnTarget.SetCompact(nBits);
|
||||
|
||||
if (bnTarget <= 0)
|
||||
return 0;
|
||||
|
||||
return ((CBigNum(1) << 256) / (bnTarget + 1)).getuint256();
|
||||
}
|
||||
|
||||
bool CSyncManager::GetKnownHeaderState(const uint256& hash, int& nHeight, uint256& nChainTrust) const
|
||||
{
|
||||
std::map<uint256, CBlockIndex*>::const_iterator miBlock = mapBlockIndex.find(hash);
|
||||
if (miBlock != mapBlockIndex.end())
|
||||
{
|
||||
nHeight = miBlock->second->nHeight;
|
||||
nChainTrust = miBlock->second->nChainTrust;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<uint256, HeaderNode>::const_iterator miHeader = mapHeaders.find(hash);
|
||||
if (miHeader != mapHeaders.end())
|
||||
{
|
||||
nHeight = miHeader->second.nHeight;
|
||||
nChainTrust = miHeader->second.nChainTrust;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CSyncManager::GetPrevHash(const uint256& hash, uint256& hashPrev) const
|
||||
{
|
||||
std::map<uint256, HeaderNode>::const_iterator miHeader = mapHeaders.find(hash);
|
||||
if (miHeader != mapHeaders.end())
|
||||
{
|
||||
hashPrev = miHeader->second.header.hashPrevBlock;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<uint256, CBlockIndex*>::const_iterator miBlock = mapBlockIndex.find(hash);
|
||||
if (miBlock != mapBlockIndex.end() && miBlock->second->pprev)
|
||||
{
|
||||
hashPrev = miBlock->second->pprev->GetBlockHash();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CSyncManager::RecomputeBestHeader()
|
||||
{
|
||||
hashBestHeader = 0;
|
||||
uint256 nBestTrust = 0;
|
||||
|
||||
for (std::map<uint256, HeaderNode>::const_iterator it = mapHeaders.begin(); it != mapHeaders.end(); ++it)
|
||||
{
|
||||
if (hashBestHeader == 0 || it->second.nChainTrust > nBestTrust)
|
||||
{
|
||||
hashBestHeader = it->first;
|
||||
nBestTrust = it->second.nChainTrust;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSyncManager::PruneHeaders()
|
||||
{
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
|
||||
if (mapHeaders.size() > MAX_HEADER_SYNC_CACHE / 2)
|
||||
{
|
||||
unsigned int nEvicted = 0;
|
||||
for (std::map<uint256, HeaderNode>::iterator it = mapHeaders.begin(); it != mapHeaders.end(); )
|
||||
{
|
||||
if (nNow - it->second.nInsertTime >= HEADER_SYNC_TTL_MICROS)
|
||||
{
|
||||
it = mapHeaders.erase(it);
|
||||
++nEvicted;
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
if (nEvicted > 0)
|
||||
{
|
||||
printf("IBD-DIAG: TTL-evicted %u stale sync headers, %u remain\n",
|
||||
nEvicted, (unsigned int)mapHeaders.size());
|
||||
RecomputeBestHeader();
|
||||
}
|
||||
}
|
||||
|
||||
if (mapHeaders.size() > MAX_HEADER_SYNC_CACHE)
|
||||
{
|
||||
printf("IBD-DIAG: sync header cache exceeded %u entries, evicting oldest\n", MAX_HEADER_SYNC_CACHE);
|
||||
while (mapHeaders.size() > MAX_HEADER_SYNC_CACHE * 3 / 4)
|
||||
{
|
||||
std::map<uint256, HeaderNode>::iterator oldest = mapHeaders.begin();
|
||||
for (std::map<uint256, HeaderNode>::iterator it = mapHeaders.begin(); it != mapHeaders.end(); ++it)
|
||||
{
|
||||
if (it->second.nInsertTime < oldest->second.nInsertTime)
|
||||
oldest = it;
|
||||
}
|
||||
mapHeaders.erase(oldest);
|
||||
}
|
||||
RecomputeBestHeader();
|
||||
}
|
||||
}
|
||||
|
||||
bool CSyncManager::AddHeaderNode(const CBlock& header, const uint256& hashHeader)
|
||||
{
|
||||
if (mapBlockIndex.count(hashHeader) || mapHeaders.count(hashHeader))
|
||||
return true;
|
||||
|
||||
if (!header.vtx.empty())
|
||||
{
|
||||
printf("IBD-DIAG: header rejected (has vtx) hash=%s\n", hashHeader.ToString().substr(0,20).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.GetBlockTime() > GetTime() + 15 * 60)
|
||||
{
|
||||
printf("IBD-DIAG: header rejected (future time) hash=%s time=%u\n",
|
||||
hashHeader.ToString().substr(0,20).c_str(), header.nTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
int nPrevHeight = -1;
|
||||
uint256 nPrevChainTrust = 0;
|
||||
if (!GetKnownHeaderState(header.hashPrevBlock, nPrevHeight, nPrevChainTrust))
|
||||
{
|
||||
printf("IBD-DIAG: header rejected (prev unknown) hash=%s prevHash=%s\n",
|
||||
hashHeader.ToString().substr(0,20).c_str(),
|
||||
header.hashPrevBlock.ToString().substr(0,20).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const int nHeight = nPrevHeight + 1;
|
||||
if (nHeight <= CUTOFF_POW_BLOCK && !CheckProofOfWork(hashHeader, header.nBits))
|
||||
{
|
||||
printf("IBD-DIAG: header PoW FAILED at height %d hash=%s nBits=%08x prevHash=%s\n",
|
||||
nHeight, hashHeader.ToString().substr(0,20).c_str(), header.nBits,
|
||||
header.hashPrevBlock.ToString().substr(0,20).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
HeaderNode node;
|
||||
node.header = header;
|
||||
node.nHeight = nHeight;
|
||||
node.nChainTrust = nPrevChainTrust + GetHeaderTrust(header.nBits);
|
||||
node.fRequested = false;
|
||||
node.nLastRequestTime = 0;
|
||||
node.nFirstRequestTime = 0;
|
||||
node.nInsertTime = GetTime() * 1000000;
|
||||
|
||||
mapHeaders.insert({hashHeader, node});
|
||||
|
||||
if (hashBestHeader == 0 || node.nChainTrust > mapHeaders[hashBestHeader].nChainTrust)
|
||||
hashBestHeader = hashHeader;
|
||||
|
||||
PruneHeaders();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint256> CSyncManager::GetDownloadPath(uint256 hashTip) const
|
||||
{
|
||||
std::vector<uint256> vPath;
|
||||
|
||||
while (hashTip != 0 && !mapBlockIndex.count(hashTip))
|
||||
{
|
||||
std::map<uint256, HeaderNode>::const_iterator mi = mapHeaders.find(hashTip);
|
||||
if (mi == mapHeaders.end())
|
||||
break;
|
||||
|
||||
vPath.push_back(hashTip);
|
||||
hashTip = mi->second.header.hashPrevBlock;
|
||||
}
|
||||
|
||||
std::reverse(vPath.begin(), vPath.end());
|
||||
return vPath;
|
||||
}
|
||||
|
||||
unsigned int CSyncManager::CountInFlight() const
|
||||
{
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
unsigned int nInFlight = 0;
|
||||
for (std::map<uint256, HeaderNode>::const_iterator it = mapHeaders.begin(); it != mapHeaders.end(); ++it)
|
||||
{
|
||||
if (it->second.fRequested && nNow - it->second.nLastRequestTime < HEADER_REQUEST_TIMEOUT_MICROS)
|
||||
++nInFlight;
|
||||
}
|
||||
return nInFlight;
|
||||
}
|
||||
|
||||
unsigned int CSyncManager::GetPlannerDepth() const
|
||||
{
|
||||
if (hashBestHeader == 0)
|
||||
return 0;
|
||||
|
||||
return (unsigned int)GetDownloadPath(hashBestHeader).size();
|
||||
}
|
||||
|
||||
int CSyncManager::GetPlannerHeight() const
|
||||
{
|
||||
if (hashBestHeader == 0)
|
||||
return pindexBest ? pindexBest->nHeight : -1;
|
||||
|
||||
std::map<uint256, HeaderNode>::const_iterator mi = mapHeaders.find(hashBestHeader);
|
||||
if (mi == mapHeaders.end())
|
||||
return pindexBest ? pindexBest->nHeight : -1;
|
||||
|
||||
return mi->second.nHeight;
|
||||
}
|
||||
|
||||
int64_t CSyncManager::GetRequestTime(const uint256& hashBlock) const
|
||||
{
|
||||
std::map<uint256, HeaderNode>::const_iterator mi = mapHeaders.find(hashBlock);
|
||||
if (mi == mapHeaders.end())
|
||||
return 0;
|
||||
return mi->second.nFirstRequestTime;
|
||||
}
|
||||
|
||||
void CSyncManager::BlockAccepted(const uint256& hashBlock)
|
||||
{
|
||||
std::map<uint256, HeaderNode>::iterator mi = mapHeaders.find(hashBlock);
|
||||
if (mi == mapHeaders.end())
|
||||
return;
|
||||
|
||||
mapHeaders.erase(mi);
|
||||
if (hashBestHeader == hashBlock)
|
||||
RecomputeBestHeader();
|
||||
}
|
||||
|
||||
void CSyncManager::ContinueHeaders(CNode* pfrom, const uint256& hashTip)
|
||||
{
|
||||
if (!pfrom || hashTip == 0)
|
||||
return;
|
||||
|
||||
std::vector<uint256> vHave;
|
||||
uint256 hashWalk = hashTip;
|
||||
int nStep = 1;
|
||||
|
||||
while (hashWalk != 0)
|
||||
{
|
||||
vHave.push_back(hashWalk);
|
||||
|
||||
for (int i = 0; i < nStep && hashWalk != 0; ++i)
|
||||
{
|
||||
uint256 hashPrev = 0;
|
||||
if (!GetPrevHash(hashWalk, hashPrev))
|
||||
hashWalk = 0;
|
||||
else
|
||||
hashWalk = hashPrev;
|
||||
}
|
||||
|
||||
if (vHave.size() > 10)
|
||||
nStep *= 2;
|
||||
}
|
||||
|
||||
vHave.push_back(!fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet);
|
||||
pfrom->PushMessage("getheaders", CBlockLocator(vHave), uint256(0));
|
||||
}
|
||||
|
||||
bool CSyncManager::RequestRefill(CNode* pfrom, uint256 hashTip, int64_t nMinIntervalSeconds, const char* pszReason)
|
||||
{
|
||||
if (!pfrom || pfrom->fClient || pfrom->nVersion == 0 || !IsInitialBlockDownload())
|
||||
return false;
|
||||
|
||||
const int64_t nNowSec = GetTime();
|
||||
if (nMinIntervalSeconds > 0 &&
|
||||
nNowSec - pfrom->nLastIbdHeaderRequest < nMinIntervalSeconds)
|
||||
return false;
|
||||
|
||||
uint256 hashLocatorTip = hashTip;
|
||||
if (hashLocatorTip == 0 ||
|
||||
(!mapBlockIndex.count(hashLocatorTip) && !mapHeaders.count(hashLocatorTip)))
|
||||
{
|
||||
hashLocatorTip = hashBestHeader;
|
||||
}
|
||||
|
||||
if (hashLocatorTip != 0 && (!pindexBest || hashLocatorTip != pindexBest->GetBlockHash()))
|
||||
{
|
||||
ContinueHeaders(pfrom, hashLocatorTip);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!pindexBest)
|
||||
return false;
|
||||
|
||||
pfrom->pindexLastGetHeadersBegin = NULL;
|
||||
pfrom->PushGetHeaders(pindexBest, uint256(0));
|
||||
hashLocatorTip = pindexBest->GetBlockHash();
|
||||
}
|
||||
|
||||
pfrom->nLastIbdHeaderRequest = nNowSec;
|
||||
printf("IBD-DIAG: %s getheaders to peer=%s locator=%s plannerDepth=%u inflight=%u\n",
|
||||
pszReason, pfrom->addr.ToString().c_str(),
|
||||
hashLocatorTip.ToString().substr(0,20).c_str(),
|
||||
GetPlannerDepth(), CountInFlight());
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int CSyncManager::RequestRefillAllPeers(uint256 hashTip, int64_t nMinIntervalSeconds, const char* pszReason)
|
||||
{
|
||||
std::vector<CNode*> vEligiblePeers;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
if (!pnode->fClient && pnode->nVersion != 0 && !pnode->fDisconnect)
|
||||
vEligiblePeers.push_back(pnode);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int nRequested = 0;
|
||||
for (CNode* pnode : vEligiblePeers)
|
||||
{
|
||||
if (RequestRefill(pnode, hashTip, nMinIntervalSeconds, pszReason))
|
||||
++nRequested;
|
||||
}
|
||||
|
||||
return nRequested;
|
||||
}
|
||||
|
||||
unsigned int CSyncManager::QueueBlocksParallel(unsigned int nWindow)
|
||||
{
|
||||
if (hashBestHeader == 0)
|
||||
return 0;
|
||||
|
||||
const std::vector<uint256> vPath = GetDownloadPath(hashBestHeader);
|
||||
if (vPath.empty())
|
||||
return 0;
|
||||
|
||||
std::vector<CNode*> vEligiblePeers;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
if (!pnode->fClient && pnode->nVersion != 0 && !pnode->fDisconnect)
|
||||
vEligiblePeers.push_back(pnode);
|
||||
}
|
||||
}
|
||||
|
||||
if (vEligiblePeers.empty())
|
||||
return 0;
|
||||
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
unsigned int nInFlight = CountInFlight();
|
||||
unsigned int nQueued = 0;
|
||||
unsigned int nPeerIndex = 0;
|
||||
|
||||
std::sort(vEligiblePeers.begin(), vEligiblePeers.end(),
|
||||
[](const CNode* a, const CNode* b) {
|
||||
return a->nBlocksDelivered > b->nBlocksDelivered;
|
||||
});
|
||||
|
||||
std::vector<CNode*> vWeightedPeers;
|
||||
for (size_t i = 0; i < vEligiblePeers.size(); i++)
|
||||
{
|
||||
int nWeight = (i == 0) ? 3 : (i == 1) ? 2 : 1;
|
||||
for (int w = 0; w < nWeight; w++)
|
||||
vWeightedPeers.push_back(vEligiblePeers[i]);
|
||||
}
|
||||
|
||||
int64_t nAdaptiveTimeout = HEADER_REQUEST_TIMEOUT_MICROS;
|
||||
{
|
||||
int64_t nTotalLatency = 0;
|
||||
int nPeersWithLatency = 0;
|
||||
for (const CNode* pnode : vEligiblePeers)
|
||||
{
|
||||
if (pnode->nAvgBlockLatencyUs > 0)
|
||||
{
|
||||
nTotalLatency += pnode->nAvgBlockLatencyUs;
|
||||
++nPeersWithLatency;
|
||||
}
|
||||
}
|
||||
if (nPeersWithLatency > 0)
|
||||
{
|
||||
int64_t nAvgLatency = nTotalLatency / nPeersWithLatency;
|
||||
nAdaptiveTimeout = std::max((int64_t)(10 * 1000000),
|
||||
std::min((int64_t)(60 * 1000000), nAvgLatency * 5));
|
||||
}
|
||||
}
|
||||
|
||||
for (std::vector<uint256>::const_iterator it = vPath.begin(); it != vPath.end(); ++it)
|
||||
{
|
||||
if (nInFlight + nQueued >= nWindow)
|
||||
break;
|
||||
|
||||
std::map<uint256, HeaderNode>::iterator mi = mapHeaders.find(*it);
|
||||
if (mi == mapHeaders.end())
|
||||
continue;
|
||||
|
||||
bool fNeedsRequest = false;
|
||||
if (!mi->second.fRequested)
|
||||
fNeedsRequest = true;
|
||||
else if (nNow - mi->second.nLastRequestTime >= nAdaptiveTimeout)
|
||||
fNeedsRequest = true;
|
||||
else if (nNow - mi->second.nLastRequestTime >= HEADER_REDUNDANT_REQUEST_MICROS)
|
||||
fNeedsRequest = true;
|
||||
|
||||
if (!fNeedsRequest)
|
||||
continue;
|
||||
|
||||
CNode* pnode = vWeightedPeers[nPeerIndex % vWeightedPeers.size()];
|
||||
pnode->AskFor(CInv(MSG_BLOCK, *it));
|
||||
|
||||
if (IsInitialBlockDownload() &&
|
||||
vWeightedPeers.size() >= 2 &&
|
||||
vWeightedPeers.size() < HEADER_REDUNDANT_PEER_THRESHOLD &&
|
||||
!mi->second.fRequested)
|
||||
{
|
||||
CNode* pnode2 = vWeightedPeers[(nPeerIndex + 1) % vWeightedPeers.size()];
|
||||
if (pnode2 != pnode)
|
||||
pnode2->AskFor(CInv(MSG_BLOCK, *it));
|
||||
}
|
||||
|
||||
if (!mi->second.fRequested || nNow - mi->second.nLastRequestTime >= HEADER_REQUEST_TIMEOUT_MICROS)
|
||||
{
|
||||
if (!mi->second.fRequested)
|
||||
mi->second.nFirstRequestTime = nNow;
|
||||
mi->second.fRequested = true;
|
||||
mi->second.nLastRequestTime = nNow;
|
||||
}
|
||||
|
||||
++nQueued;
|
||||
++nPeerIndex;
|
||||
}
|
||||
|
||||
if (nQueued > 0)
|
||||
printf("IBD-DIAG: sync manager queued %u blocks across %zu peers (window=%u, inflight=%u)\n",
|
||||
nQueued, vEligiblePeers.size(), nWindow, nInFlight);
|
||||
|
||||
return nQueued;
|
||||
}
|
||||
|
||||
bool CSyncManager::ProcessHeaders(CNode* pfrom, const std::vector<CBlock>& vHeaders)
|
||||
{
|
||||
if (vHeaders.size() > 2000)
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("message headers size() = %" PRIszu "", vHeaders.size());
|
||||
}
|
||||
|
||||
uint256 hashChainTip = 0;
|
||||
int nNewHeaders = 0;
|
||||
for (const CBlock& header : vHeaders)
|
||||
{
|
||||
if (!header.vtx.empty())
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("headers message includes transactions");
|
||||
}
|
||||
|
||||
const uint256 hashHeader = header.GetHash();
|
||||
if (mapBlockIndex.count(hashHeader) || mapHeaders.count(hashHeader))
|
||||
{
|
||||
hashChainTip = hashHeader;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hashChainTip != 0)
|
||||
{
|
||||
if (header.hashPrevBlock != hashChainTip)
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("non-continuous headers sequence");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(header.hashPrevBlock);
|
||||
if (miPrev == mapBlockIndex.end() && !mapHeaders.count(header.hashPrevBlock))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!AddHeaderNode(header, hashHeader))
|
||||
{
|
||||
pfrom->Misbehaving(20);
|
||||
return error("invalid header sequence");
|
||||
}
|
||||
|
||||
hashChainTip = hashHeader;
|
||||
nNewHeaders++;
|
||||
}
|
||||
|
||||
int nRequested = 0;
|
||||
if (hashBestHeader != 0)
|
||||
nRequested = QueueBlocksParallel(HEADER_DOWNLOAD_WINDOW);
|
||||
|
||||
if (nNewHeaders > 0)
|
||||
nLastNewHeaderTime = GetTime();
|
||||
|
||||
if (nNewHeaders > 0 || nRequested > 0)
|
||||
printf("IBD-DIAG: accepted %d new headers, queued %d blocks from %zu headers (peer=%s bestHeader=%s)\n",
|
||||
nNewHeaders, nRequested, vHeaders.size(), pfrom->addr.ToString().c_str(),
|
||||
hashBestHeader.ToString().substr(0,20).c_str());
|
||||
|
||||
if (vHeaders.size() >= 2000)
|
||||
{
|
||||
if (IsInitialBlockDownload() && hashChainTip != 0)
|
||||
ContinueHeaders(pfrom, hashChainTip);
|
||||
else
|
||||
pfrom->PushGetBlocks(pindexBest, uint256(0));
|
||||
}
|
||||
else if (IsInitialBlockDownload() && nNewHeaders > 0 && hashChainTip != 0)
|
||||
{
|
||||
ContinueHeaders(pfrom, hashChainTip);
|
||||
}
|
||||
else if (IsInitialBlockDownload())
|
||||
{
|
||||
const unsigned int nPlannerDepth = GetPlannerDepth();
|
||||
if (nPlannerDepth <= HEADER_SYNC_LOW_WATER)
|
||||
RequestRefill(
|
||||
pfrom, (hashChainTip != 0) ? hashChainTip : hashBestHeader,
|
||||
HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
(nPlannerDepth == 0) ? "headers planner empty" : "headers planner low-water");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSyncManager::TrackBlockDelivery(CNode* pfrom, const uint256& hashBlock)
|
||||
{
|
||||
if (!pfrom)
|
||||
return;
|
||||
|
||||
pfrom->nBlocksDelivered++;
|
||||
if (nBestHeight > pfrom->nBestKnownHeight)
|
||||
pfrom->nBestKnownHeight = nBestHeight;
|
||||
|
||||
int64_t nRequestTime = GetRequestTime(hashBlock);
|
||||
if (nRequestTime > 0)
|
||||
{
|
||||
int64_t nLatency = GetTime() * 1000000 - nRequestTime;
|
||||
if (nLatency > 0)
|
||||
{
|
||||
if (pfrom->nAvgBlockLatencyUs == 0)
|
||||
pfrom->nAvgBlockLatencyUs = nLatency;
|
||||
else
|
||||
pfrom->nAvgBlockLatencyUs = (pfrom->nAvgBlockLatencyUs * 7 + nLatency) / 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSyncManager::Tick(CNode* pto, int nHighestInvWalk, const uint256& hashHighestInvWalk)
|
||||
{
|
||||
if (!pto || pto->fClient || pto->nVersion == 0 || !IsInitialBlockDownload())
|
||||
return;
|
||||
|
||||
const int64_t nNowSec = GetTime();
|
||||
const unsigned int nPlannerDepth = GetPlannerDepth();
|
||||
const unsigned int nInFlight = CountInFlight();
|
||||
static int64_t nLastHeaderPlannerControl = 0;
|
||||
static int64_t nLastHeaderWatchdog = 0;
|
||||
static int64_t nLastBlockPlannerControl = 0;
|
||||
|
||||
if (nLastNewHeaderTime == 0)
|
||||
nLastNewHeaderTime = nNowSec;
|
||||
|
||||
if (nNowSec - nLastHeaderPlannerControl >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||
nPlannerDepth < HEADER_SYNC_LOW_WATER &&
|
||||
nInFlight < HEADER_SYNC_TARGET_INFLIGHT)
|
||||
{
|
||||
const unsigned int nRefilled = RequestRefillAllPeers(
|
||||
hashBestHeader, HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
"control-loop");
|
||||
if (nRefilled > 0)
|
||||
printf("IBD-DIAG: control-loop refill from %u peers (plannerDepth=%u inflight=%u target=%u)\n",
|
||||
nRefilled, nPlannerDepth, nInFlight, HEADER_SYNC_TARGET_INFLIGHT);
|
||||
nLastHeaderPlannerControl = nNowSec;
|
||||
}
|
||||
|
||||
if (nNowSec - nLastHeaderWatchdog >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||
nNowSec - nLastNewHeaderTime >= HEADER_SYNC_WATCHDOG_SECONDS)
|
||||
{
|
||||
const unsigned int nRefilled = RequestRefillAllPeers(
|
||||
hashBestHeader, HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS,
|
||||
"headers-watchdog");
|
||||
if (nRefilled > 0)
|
||||
printf("IBD-DIAG: headers watchdog refill from %u peers after %llds without new headers (plannerDepth=%u inflight=%u)\n",
|
||||
nRefilled,
|
||||
(long long)(nNowSec - nLastNewHeaderTime),
|
||||
nPlannerDepth,
|
||||
nInFlight);
|
||||
nLastHeaderWatchdog = nNowSec;
|
||||
}
|
||||
|
||||
const int64_t nMinInterval = (mapHeaders.size() < HEADER_DOWNLOAD_WINDOW) ? 15 : 60;
|
||||
if (nNowSec - pto->nLastIbdHeaderRequest >= nMinInterval)
|
||||
RequestRefill(pto, hashBestHeader, nMinInterval, "heartbeat");
|
||||
|
||||
if (nNowSec - nLastBlockPlannerControl >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
|
||||
hashBestHeader != 0 &&
|
||||
nPlannerDepth > 0)
|
||||
{
|
||||
const unsigned int nRequeued = QueueBlocksParallel(HEADER_DOWNLOAD_WINDOW);
|
||||
if (nRequeued > 0)
|
||||
printf("IBD-DIAG: block-planner control queued %u block requests (plannerDepth=%u inflight=%u)\n",
|
||||
nRequeued, nPlannerDepth, nInFlight);
|
||||
nLastBlockPlannerControl = nNowSec;
|
||||
}
|
||||
|
||||
if (hashBestHeader == 0 && nHighestInvWalk > nBestHeight &&
|
||||
hashHighestInvWalk != 0 && mapBlockIndex.count(hashHighestInvWalk))
|
||||
{
|
||||
RequestRefill(pto, hashHighestInvWalk, HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS, "inv-walk bridge");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2026 The Triangles developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#ifndef TRIANGLES_SYNCMANAGER_H
|
||||
#define TRIANGLES_SYNCMANAGER_H
|
||||
|
||||
#include "uint256.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class CBlock;
|
||||
class CInv;
|
||||
class CNode;
|
||||
|
||||
class CSyncManager
|
||||
{
|
||||
public:
|
||||
struct HeaderNode;
|
||||
|
||||
static constexpr unsigned int HEADER_DOWNLOAD_WINDOW = 1024;
|
||||
static constexpr unsigned int HEADER_SYNC_LOW_WATER = HEADER_DOWNLOAD_WINDOW / 4;
|
||||
static constexpr unsigned int HEADER_SYNC_TARGET_INFLIGHT = HEADER_DOWNLOAD_WINDOW / 2;
|
||||
static constexpr int64_t HEADER_SYNC_REFILL_MIN_INTERVAL_SECONDS = 5;
|
||||
static constexpr int64_t HEADER_SYNC_CONTROL_INTERVAL_SECONDS = 5;
|
||||
static constexpr int64_t HEADER_SYNC_WATCHDOG_SECONDS = 25;
|
||||
|
||||
bool HaveHeader(const uint256& hash) const;
|
||||
uint256 GetBestHeader() const;
|
||||
std::size_t GetHeaderCount() const;
|
||||
unsigned int CountInFlight() const;
|
||||
unsigned int GetPlannerDepth() const;
|
||||
int GetPlannerHeight() const;
|
||||
int64_t GetRequestTime(const uint256& hashBlock) const;
|
||||
|
||||
bool RequestRefill(CNode* pfrom, uint256 hashTip, int64_t nMinIntervalSeconds, const char* pszReason);
|
||||
unsigned int RequestRefillAllPeers(uint256 hashTip, int64_t nMinIntervalSeconds, const char* pszReason);
|
||||
unsigned int QueueBlocksParallel(unsigned int nWindow = HEADER_DOWNLOAD_WINDOW);
|
||||
bool ProcessHeaders(CNode* pfrom, const std::vector<CBlock>& vHeaders);
|
||||
void BlockAccepted(const uint256& hashBlock);
|
||||
void TrackBlockDelivery(CNode* pfrom, const uint256& hashBlock);
|
||||
void Tick(CNode* pto, int nHighestInvWalk, const uint256& hashHighestInvWalk);
|
||||
|
||||
private:
|
||||
uint256 GetHeaderTrust(unsigned int nBits) const;
|
||||
bool GetKnownHeaderState(const uint256& hash, int& nHeight, uint256& nChainTrust) const;
|
||||
bool GetPrevHash(const uint256& hash, uint256& hashPrev) const;
|
||||
void RecomputeBestHeader();
|
||||
void PruneHeaders();
|
||||
bool AddHeaderNode(const CBlock& header, const uint256& hashHeader);
|
||||
std::vector<uint256> GetDownloadPath(uint256 hashTip) const;
|
||||
void ContinueHeaders(CNode* pfrom, const uint256& hashTip);
|
||||
};
|
||||
|
||||
extern CSyncManager g_syncManager;
|
||||
|
||||
#endif // TRIANGLES_SYNCMANAGER_H
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp>
|
||||
#include <chrono>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "main.h"
|
||||
@@ -249,25 +249,23 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig)
|
||||
tx.vin[j].prevout.hash = orphans[j].GetHash();
|
||||
}
|
||||
// Creating signatures primes the cache:
|
||||
boost::posix_time::ptime mst1 = boost::posix_time::microsec_clock::local_time();
|
||||
auto mst1 = std::chrono::steady_clock::now();
|
||||
for (unsigned int j = 0; j < tx.vin.size(); j++)
|
||||
BOOST_CHECK(SignSignature(keystore, orphans[j], tx, j));
|
||||
boost::posix_time::ptime mst2 = boost::posix_time::microsec_clock::local_time();
|
||||
boost::posix_time::time_duration msdiff = mst2 - mst1;
|
||||
long nOneValidate = msdiff.total_milliseconds();
|
||||
auto mst2 = std::chrono::steady_clock::now();
|
||||
long nOneValidate = std::chrono::duration_cast<std::chrono::milliseconds>(mst2 - mst1).count();
|
||||
if (fDebug) printf("DoS_Checksig sign: %ld\n", nOneValidate);
|
||||
|
||||
// ... now validating repeatedly should be quick:
|
||||
// 2.8GHz machine, -g build: Sign takes ~760ms,
|
||||
// uncached Verify takes ~250ms, cached Verify takes ~50ms
|
||||
// (for 100 single-signature inputs)
|
||||
mst1 = boost::posix_time::microsec_clock::local_time();
|
||||
mst1 = std::chrono::steady_clock::now();
|
||||
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, SIGHASH_ALL));
|
||||
mst2 = boost::posix_time::microsec_clock::local_time();
|
||||
msdiff = mst2 - mst1;
|
||||
long nManyValidate = msdiff.total_milliseconds();
|
||||
mst2 = std::chrono::steady_clock::now();
|
||||
long nManyValidate = std::chrono::duration_cast<std::chrono::milliseconds>(mst2 - mst1).count();
|
||||
if (fDebug) printf("DoS_Checksig five: %ld\n", nManyValidate);
|
||||
|
||||
BOOST_CHECK_MESSAGE(nManyValidate < nOneValidate, "Signature cache timing failed");
|
||||
|
||||
@@ -13,7 +13,7 @@ GetResults(CWalletDB& walletdb, std::map<int64_t, CAccountingEntry>& results)
|
||||
std::list<CAccountingEntry> aes;
|
||||
|
||||
results.clear();
|
||||
BOOST_CHECK(walletdb.ReorderTransactions(pwalletMain) == DB_LOAD_OK);
|
||||
BOOST_CHECK(walletdb.ReorderTransactions(pwalletMain.get()) == DB_LOAD_OK);
|
||||
walletdb.ListAccountCreditDebit("", aes);
|
||||
for (CAccountingEntry& ae : aes)
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ struct {
|
||||
// NOTE: These tests rely on CreateNewBlock doing its own self-validation!
|
||||
BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
|
||||
{
|
||||
CReserveKey reservekey(pwalletMain);
|
||||
CReserveKey reservekey(pwalletMain.get());
|
||||
CBlock *pblock;
|
||||
CTransaction tx;
|
||||
CScript script;
|
||||
|
||||
@@ -181,7 +181,7 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1)
|
||||
|
||||
{
|
||||
vector<valtype> solutions;
|
||||
txnouttype whichType;
|
||||
TxnOutType whichType;
|
||||
CScript s;
|
||||
s << key[0].GetPubKey() << OP_CHECKSIG;
|
||||
BOOST_CHECK(Solver(s, whichType, solutions));
|
||||
@@ -194,7 +194,7 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1)
|
||||
}
|
||||
{
|
||||
vector<valtype> solutions;
|
||||
txnouttype whichType;
|
||||
TxnOutType whichType;
|
||||
CScript s;
|
||||
s << OP_DUP << OP_HASH160 << key[0].GetPubKey().GetID() << OP_EQUALVERIFY << OP_CHECKSIG;
|
||||
BOOST_CHECK(Solver(s, whichType, solutions));
|
||||
@@ -207,7 +207,7 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1)
|
||||
}
|
||||
{
|
||||
vector<valtype> solutions;
|
||||
txnouttype whichType;
|
||||
TxnOutType whichType;
|
||||
CScript s;
|
||||
s << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG;
|
||||
BOOST_CHECK(Solver(s, whichType, solutions));
|
||||
@@ -220,7 +220,7 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1)
|
||||
}
|
||||
{
|
||||
vector<valtype> solutions;
|
||||
txnouttype whichType;
|
||||
TxnOutType whichType;
|
||||
CScript s;
|
||||
s << OP_1 << key[0].GetPubKey() << key[1].GetPubKey() << OP_2 << OP_CHECKMULTISIG;
|
||||
BOOST_CHECK(Solver(s, whichType, solutions));
|
||||
@@ -237,7 +237,7 @@ BOOST_AUTO_TEST_CASE(multisig_Solver1)
|
||||
}
|
||||
{
|
||||
vector<valtype> solutions;
|
||||
txnouttype whichType;
|
||||
TxnOutType whichType;
|
||||
CScript s;
|
||||
s << OP_2 << key[0].GetPubKey() << key[1].GetPubKey() << key[2].GetPubKey() << OP_3 << OP_CHECKMULTISIG;
|
||||
BOOST_CHECK(Solver(s, whichType, solutions));
|
||||
|
||||
@@ -85,7 +85,7 @@ ParseScript(string s)
|
||||
Array
|
||||
read_json(const std::string& filename)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
fs::path testFile = fs::current_path() / "test" / "data" / filename;
|
||||
|
||||
#ifdef TEST_DATA_DIR
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
#include "anonymize.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/thread/thread.hpp>
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
@@ -42,10 +41,10 @@ int check_interrupted(
|
||||
) ? 1 : 0;
|
||||
}
|
||||
|
||||
static boost::mutex initializing;
|
||||
static std::mutex initializing;
|
||||
|
||||
static std::unique_ptr<boost::unique_lock<boost::mutex> > uninitialized(
|
||||
new boost::unique_lock<boost::mutex>(
|
||||
static std::unique_ptr<std::unique_lock<std::mutex> > uninitialized(
|
||||
new std::unique_lock<std::mutex>(
|
||||
initializing
|
||||
)
|
||||
);
|
||||
@@ -57,5 +56,5 @@ void set_initialized(
|
||||
|
||||
void wait_initialized(
|
||||
) {
|
||||
boost::unique_lock<boost::mutex> checking(initializing);
|
||||
std::unique_lock<std::mutex> checking(initializing);
|
||||
}
|
||||
|
||||
+14
-13
@@ -34,7 +34,7 @@
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
@@ -54,18 +54,19 @@
|
||||
#endif
|
||||
|
||||
// Ensure we have the global wallet pointer
|
||||
extern CWallet* pwalletMain;
|
||||
#include <memory>
|
||||
extern std::unique_ptr<CWallet> pwalletMain;
|
||||
|
||||
// Static instance
|
||||
CTorV3Manager* CTorV3Manager::instance = nullptr;
|
||||
static TorV3Config torV3Config;
|
||||
|
||||
static boost::filesystem::path GetBackendHiddenServiceDir(const std::string& torDataDir)
|
||||
static std::filesystem::path GetBackendHiddenServiceDir(const std::string& torDataDir)
|
||||
{
|
||||
return boost::filesystem::path(torDataDir) / "hidden_service";
|
||||
return std::filesystem::path(torDataDir) / "hidden_service";
|
||||
}
|
||||
|
||||
static bool ReadTrimmedFirstLine(const boost::filesystem::path& path, std::string& valueOut)
|
||||
static bool ReadTrimmedFirstLine(const std::filesystem::path& path, std::string& valueOut)
|
||||
{
|
||||
valueOut.clear();
|
||||
|
||||
@@ -573,12 +574,12 @@ bool CTorV3Service::AttachToBackendService(const std::string& torDataDir, int se
|
||||
|
||||
port = servicePort;
|
||||
|
||||
const boost::filesystem::path serviceDir = GetBackendHiddenServiceDir(torDataDir);
|
||||
const boost::filesystem::path hostnamePath = serviceDir / "hostname";
|
||||
const std::filesystem::path serviceDir = GetBackendHiddenServiceDir(torDataDir);
|
||||
const std::filesystem::path hostnamePath = serviceDir / "hostname";
|
||||
|
||||
std::string backendOnion;
|
||||
for (int waited = 0; waited <= waitSeconds; ++waited) {
|
||||
if (boost::filesystem::exists(hostnamePath) &&
|
||||
if (std::filesystem::exists(hostnamePath) &&
|
||||
ReadTrimmedFirstLine(hostnamePath, backendOnion)) {
|
||||
break;
|
||||
}
|
||||
@@ -616,8 +617,8 @@ bool CTorV3Service::AttachToBackendService(const std::string& torDataDir, int se
|
||||
|
||||
// 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::filesystem::path secretKeyPath = serviceDir / "hs_ed25519_secret_key";
|
||||
if (std::filesystem::exists(secretKeyPath)) {
|
||||
std::ifstream keyFile(secretKeyPath.string().c_str(), std::ios::binary);
|
||||
if (keyFile.is_open()) {
|
||||
std::vector<unsigned char> keyData(
|
||||
@@ -997,7 +998,7 @@ bool CTorV3Service::ValidateOnionAddress(const std::string& address)
|
||||
bool CTorV3Service::ExtractKeysFromHex(const std::string& privKeyHex, unsigned char* privKey, unsigned char* pubKey)
|
||||
{
|
||||
if (!privKey || !pubKey) {
|
||||
printf("ERROR: NULL pointers passed to ExtractKeysFromHex\n");
|
||||
printf("ERROR: nullptr pointers passed to ExtractKeysFromHex\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1063,7 +1064,7 @@ bool CTorV3Service::ExtractKeysFromHex(const std::string& privKeyHex, unsigned c
|
||||
bool CTorV3Service::DerivePublicKeyFromPrivate(const unsigned char* privateKey, unsigned char* publicKey)
|
||||
{
|
||||
if (!privateKey || !publicKey) {
|
||||
printf("ERROR: NULL pointer passed to DerivePublicKeyFromPrivate\n");
|
||||
printf("ERROR: nullptr pointer passed to DerivePublicKeyFromPrivate\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1218,7 +1219,7 @@ bool CTorV3Manager::InitializeTor()
|
||||
torDataDir = torV3Config.torDataDirectory;
|
||||
|
||||
// Create tor data directory
|
||||
boost::filesystem::create_directories(torDataDir);
|
||||
std::filesystem::create_directories(torDataDir);
|
||||
|
||||
torEnabled = true;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user