Compare commits

..

38 Commits

Author SHA1 Message Date
Krystie 372b252294 Fix Windows CI: use bash shell for git submodule commands
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / test-linux-sanitizers (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled
On Windows MSYS2 runners, the default shell is 'msys2' which doesn't
understand 'git submodule' commands the same way. Adding shell: bash
forces the step to use bash, which properly executes git and finds
submodule content.

Affected jobs: build-windows-qt, build-windows-daemon
2026-04-30 01:27:08 -07:00
Krystie 6c56e41e82 Fix CI: init secp256k1 submodule before build
The secp256k1 submodule (src/secp256k1/) was not being checked out
by the default shallow checkout, causing CMake to fail with:
  'src/secp256k1 is empty. Run: git submodule update --init --recursive'

All 6 build jobs (Linux unit/sanitizer, Windows Qt/daemon, Linux Qt/daemon,
macOS) now:
1. Use fetch-depth:0 to get full git history (needed for submodules)
2. Run 'git submodule update --init --recursive' after checkout
3. Proceed with the normal build steps
2026-04-30 01:08:27 -07:00
Krystie 79b0c4a176 Fix RPC thread crash on bad auth (T001)
- HTTPAuthorized: validate strAuth length before substr(6), wrap DecodeBase64 in try-catch
- RPCAcceptHandler: wrap body in try-catch to ensure counter decrement and conn cleanup
- ThreadRPCServer3: wrap while loop in try-catch for graceful exception handling

Bad auth attempts now return HTTP 401 without killing the RPC listener.
2026-04-29 19:30:13 -07:00
Krystie 3db537d759 Update T012 status: design complete 2026-04-29 19:19:35 -07:00
Krystie 63be053b1d Add TRI v6 autonomous development task queue 2026-04-29 19:06:22 -07:00
Krystie d0fb2dc105 Enable auto-bootstrap for GUI (Windows) wallets
Previously the bootstrap auto-download was guarded by #ifndef QT_GUI,
meaning the Windows Qt wallet would never auto-bootstrap on fresh installs.
This left GUI users stuck at block ~570 during IBD with no way to recover.

Now both GUI and daemon builds automatically download bootstrap data from
bootstrap.cryptographic-triangles.org when no blockchain data is found.
Progress is shown in the GUI status bar via uiInterface.InitMessage.
2026-04-29 17:18:44 -07:00
Krystie bea3c4447c Bump version to v5.9.7.0 2026-04-29 16:48:16 -07:00
Krystie 89a480a85a Fix tor_data/state directory trap + make -notor actually work
1. tor_process.cpp: Auto-recover legacy 'state' subdirectory
   - Old builds created tor_data/state/ as a directory and set
     DataDirectory to point at it. Tor 0.4.9+ rejects this because
     it expects to write a 'state' FILE inside DataDirectory.
   - Fix: Point DataDirectory at tor_data/ itself. On startup,
     if a legacy 'state/' directory exists, migrate contents up
     and remove it.

2. init.cpp: Allow -notor to actually bypass Tor requirement
   - Previously, -notor made StartEmbeddedTor() return false,
     which hit the 'Tor failed to start' error path and killed
     the wallet. Now -notor enables clearnet-only mode for
     diagnostics, benchmarking, and recovery.
   - Updated help text to reflect actual behavior.
2026-04-29 16:39:59 -07:00
sami7777 47e358dc18 Fix crypter.h missing <openssl/crypto.h> include for OPENSSL_cleanse
Latent header-hygiene bug: crypter.h calls OPENSSL_cleanse at lines 99-100
but never declared the dependency. Built fine because the precompiled
header on triangles_common pulled in <openssl/crypto.h> transitively, so
every translation unit that included crypter.h also got the symbol.

Surfaced by enabling -DBUILD_TESTS=ON: test_triangles is configured
without REUSE_FROM the PCH, so test/sigopcount_tests.cpp fails to find
OPENSSL_cleanse when crypter.h is reached transitively via key.h/wallet.h.

Adding the explicit include is the principled fix — headers should
declare their own dependencies rather than rely on the consumer's
precompiled-header configuration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:30:41 -07:00
Krystie 47c9293849 Update onion seeds with persistent Contabo addresses
Contabo seed nodes now have persistent Tor hidden service volumes.
New onion addresses:
- seed-1: vmepp7...qtpfad
- seed-2: nsldmf...uykqd
- seed-3: on4nok...y3eqd
- seed-4: 3uyzlt...iqad

Also added Hetzner Helsinki (nawqqo...j26taid).
2026-04-29 11:09:09 -07:00
Krystie 0f5582f100 Update hardcoded onion seeds with all working nodes
Replace stale/unknown onion seeds with verified working nodes:
- DNS2: gxvrhv3... (primary bootstrap server)
- DNS3: i6tk7so... (canonical chain reference)
- Contabo seeds 1-4: cuazg... 2szbe...

Also updates seeds.cryptographic-triangles.org/seeds.txt dynamically.
2026-04-29 11:09:03 -07:00
sami7777 3a78a6baf9 Merge origin/master (Krystie's RocksDB+IBD integration + snapshot wiring)
Reconciles two parallel implementations of multi-backend chain DB:
local kept its MakeChainDB factory + std::filesystem + unconditional
RocksDB + abstracted utxosnapshot, since those are downstream of the
boost-cleanup, smessage-RocksDB-port, and CTxDBBase abstraction work.

Preserved from origin (Krystie's branch):
- Block 2,203,594 checkpoint and matching mapSnapshotHashes entry
  for P2P snapshot verification (src/checkpoints.cpp)
- Headers-first IBD stall-recovery path: during IBD, replace the
  legacy PushGetBlocks fallback with RequestHeaderSyncRefillAllPeers
  + QueueHeaderSyncBlocksParallel so a stall on a weak peer set
  doesn't park at a low common ancestor (src/main.cpp SendMessages)

Discarded from origin:
- src/txdb.cpp (CActiveTxDB wrapper) — superseded by txdb-factory.cpp
- BUILD_ROCKSDB-gated paths and inline LevelDB+RocksDB code in
  utxosnapshot.cpp — already factored out behind CTxDBBase
- Public ReadRawBytes/WriteRawBytes/... wrappers added to
  CTxDBBase for CActiveTxDB; no remaining callers
- GetActiveChainDbDirName / UseRocksDbBackend in bootstrap.cpp;
  switched to GetChainDataDir()

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:04:00 -07:00
sami7777 0f2cf711db Update secp256k1 submodule pointer to v0.7.1 (1a53f49)
Aligns the recorded commit with the v0.7.1 tag actually checked out
in the working tree. Carrying forward; no code change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 03:07:14 -07:00
sami7777 55c202516d WIP: migrate ECDSA/ECDH off OpenSSL EC to libsecp256k1
Add libsecp256k1 v0.7.1 as src/secp256k1 submodule and introduce
crypto_ecdsa / crypto_ecdh wrappers as drop-in replacements for the
OpenSSL ECDSA_verify / ECDSA_sign / ECDH_compute_key call sites used
by key.cpp and smessage.cpp. Wrappers preserve on-chain compatibility
(lax DER parsing, 65-byte recoverable compact sigs, SEC1 priv-key
DER round-trip, raw-X ECDH output for smsg KDF).

CMake wires the submodule and new sources into the build. Mid-refactor;
landing as a checkpoint before stacking sync-pipeline work on top.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 02:55:18 -07:00
Krystie 4e1a0576e1 Wire UTXO snapshot hash for height 2203594 + checkpoint
- Added canonical snapshot SHA256 to mapSnapshotHashes
- Added checkpoint at block 2,203,594
- Enables P2P snapshot fetch for new node bootstrapping
2026-04-29 02:03:34 -07:00
Krystie 6d70b41844 RocksDB+IBD integration: CActiveTxDB wrapper, dual-backend utxosnapshot, headers-first IBD patch, backend-aware bootstrap
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled
2026-04-28 21:50:14 -07:00
sami7777 4fa30abb4b Auto-migrate legacy LevelDB smsgDB to RocksDB on startup
Build All Platforms / test-linux-unit (push) Waiting to run
Build All Platforms / test-linux-sanitizers (push) Waiting to run
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-linux-daemon (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Blocked by required conditions
Pre-v5.10 the secure-messaging store was backed by LevelDB at
<datadir>/smsgDB/. Phase 3a switched it to RocksDB; existing nodes
upgrading to v5.10 would otherwise lose their pubkey cache and
inbox/outbox because RocksDB can't open a LevelDB tree.

Detection: presence of CURRENT without IDENTITY in smsgDB/. RocksDB
writes IDENTITY on first open; LevelDB never does.

Migration path:
  1. Atomic rename smsgDB/ → smsgDB.leveldb-backup/
  2. Open backup with leveldb::DB (read-only)
  3. Open smsgDB/ with rocksdb::DB (create_if_missing)
  4. Iterate every key, copy in 5000-entry batches
  5. Leave the backup in place — never deleted by the migration code,
     so the user can roll back manually if needed

Triggered lazily inside SecMsgDB::Open so no separate flag or RPC is
needed. Already-migrated nodes (IDENTITY present) skip the path. Once
all users are on v5.10+ the helper and the leveldb headers it pulls
in can be dropped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 19:19:38 -07:00
sami7777 68a86c38b5 Add <algorithm> include to bignum.h
bignum.h calls std::reverse and std::reverse_copy unqualified, relying
on ADL plus <algorithm> being transitively pulled in by an earlier
header. The Qt build path on Windows MSYS2 doesn't satisfy that
assumption — the moc-generated TUs reach bignum.h before <algorithm>
shows up via any other include. Add the explicit include.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 19:14:39 -07:00
sami7777 3099371864 Fix three CI breakages on the C++20 baseline
1. rocksdb::WriteBatch::Handler typeinfo missing on Ubuntu's librocksdb-dev.
   Both SecMsgBatchScanner (smessage.cpp) and CRocksBatchScanner
   (txdb-rocksdb.cpp) inherited from Handler to scan an active WriteBatch
   for pending writes/deletes; that subclass-based scan fails to link
   because Ubuntu's package hides the parent's typeinfo. Replaced both
   scanners with a parallel std::map<std::string, std::optional<std::string>>
   maintained alongside each WriteBatch — Put adds a value entry, Delete
   adds a nullopt entry, ScanBatch becomes an O(log n) map lookup. Same
   semantics, no Handler dependency.

2. macOS Homebrew's RocksDB 10.x removed the raw DB** overload of
   DB::Open; only std::unique_ptr<DB>* remains. txdb-rocksdb.cpp called
   the raw form, breaking the macOS build. Added the same SFINAE Open
   wrapper used in smessage.cpp (commit 4265343) that picks whichever
   overload the linked rocksdb actually has.

3. CSignal<>'s SignalState::slots member collided with Qt's `#define slots`
   to empty, stripping the member name in any TU that pulls in <QtCore>
   (e.g. moc-generated files that include util_signal.h transitively).
   Renamed to slot_map.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 19:03:43 -07:00
sami7777 42653434e0 Fix CI build errors uncovered after C++20 bump
Two issues surfaced once configure stopped failing:

1. CTxDBBase::NewIterator() was protected, but the snapshot dump/load
   code (commits 76579e3, ccfada5) calls it externally. Moved to public —
   the iterator interface is intentional public API.

2. RocksDB DB::Open's raw DB** overload was removed in newer releases.
   Homebrew's macOS package (10.x) only exposes the std::unique_ptr<DB>*
   form; Ubuntu 22.04 (rocksdb 6.x) and MSYS2 (8/9.x) still expose DB**.
   Added a SFINAE wrapper OpenSmsgDB() in smessage.cpp that picks
   whichever overload the linked rocksdb actually has, so we don't need
   version macros or per-distro #ifdefs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:45:57 -07:00
sami7777 25475d1057 Fix C++20 build: allocators + bundled LevelDB
C++20 broke two things in the prior bump:

1. std::allocator no longer exposes pointer/const_pointer/reference/
   const_reference member typedefs, and the 2-arg allocate(n, hint) was
   removed. Both secure_allocator and zero_after_free_allocator inherited
   these from std::allocator. Define the typedefs ourselves and switch
   the secure_allocator allocate() to the single-arg form.

2. Bundled src/leveldb uses `std::memory_order::memory_order_relaxed`
   which was valid in C++17 but became a hard error in C++20 (memory_order
   is now a scoped enum class — the values are at namespace scope or
   memory_order::relaxed, not memory_order::memory_order_relaxed). LevelDB
   itself only needs C++11, so pin its targets to C++17 in BuildLevelDB.cmake
   instead of patching vendored code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:34:38 -07:00
sami7777 ce27e8e5cf Add auditsignatures RPC for ECDSA-path regression testing
Walks the active chain in [start_height, end_height] and runs the existing
VerifySignature path on every non-coinbase input. Returns counts plus the
first 100 failures.

Intended use: capture a pre-migration baseline (should be all-zero
failures), then re-run after switching the underlying ECDSA primitive
(e.g. OpenSSL EC -> libsecp256k1) to catch behavioural regressions before
they hit IBD on a peer.

Defaults: start = max(1, tip-1000), end = tip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:29:41 -07:00
sami7777 b17a004b83 Bump C++ standard to 20; add manual RocksDB find fallback
Two CI failures from the prior push:

1. MSYS2 mingw64's RocksDB headers (8.x+) use `using enum` and defaulted
   operator== on user-defined types — both C++20-only. Bumped
   CMAKE_CXX_STANDARD from 17 to 20 across the project. GCC 11.4 (Ubuntu),
   GCC 14.x (MSYS2), and Apple Clang 16 all support what we need.

2. Ubuntu 22.04's librocksdb-dev ships neither a CMake config package nor
   a rocksdb.pc file, so both find_package(RocksDB CONFIG) and
   pkg_check_modules(rocksdb) fail. Added a manual find_path/find_library
   fallback that creates a RocksDB::rocksdb IMPORTED target from the
   raw header dir + .so, with a clear FATAL_ERROR if all three probes miss.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:27:49 -07:00
sami7777 ccfada5ca9 Port LoadSnapshot + reindex/bootstrap-guard to chain DB abstraction
LoadSnapshot previously opened LevelDB directly at <datadir>/txleveldb to
write the snapshot in. Refactored to use the CTxDBBase abstraction:
- WipeChainDataDir() removes the configured backend's chain DB dir
- MakeChainDB("c+") opens fresh via the factory
- High-level methods (WriteBlockIndex, WriteUtxo, WriteHashBestChain,
  WriteVersion, WriteDbFormat) replace manual key/value construction
- TxnBegin/Commit cycles every 1000 headers / 50000 UTXOs preserve the
  prior batching cadence

The IsRocksDbChainBackend() guard added in 76579e3 is dropped — snapshot
loading now works on either backend.

Two adjacent paths in init.cpp also hardcoded "txleveldb": the snapshot
auto-load guard (Step 6c) and the -reindex datadir wipe. Both updated to
GetChainDataDir() / WipeChainDataDir() so they pick the right directory
for the configured backend.

Helpers added to txdb.h / txdb-factory.cpp:
- GetChainDataDir(): on-disk path of the configured backend's chain DB
- WipeChainDataDir(): rm -rf the same path

Bootstrap archive paths (bootstrap.cpp lines 721+) intentionally still
reference txleveldb specifically — the prebuilt-index distribution
remains LevelDB-format until that pipeline is ported separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:19:46 -07:00
sami7777 59ee532bf6 Port smessage to RocksDB, make RocksDB a hard dep
The secure-messaging store (smsgDB) used the LevelDB API directly. Mass-
mapped to the equivalent RocksDB types: leveldb::DB/Status/WriteBatch/
Iterator/Slice/ReadOptions/WriteOptions/WriteBatch::Handler -> rocksdb::*.
The RocksDB API surface for our usage is binary-compatible — pure namespace
substitution, no semantic changes. Consumers in rpcsmessage.cpp and
qt/messagemodel.cpp updated to match.

RocksDB now becomes a hard build dependency (was optional behind
BUILD_ROCKSDB). The chain-DB rocksdb backend is consequently always
available; -chaindb=leveldb remains the default until the Phase-4
LevelDB retirement. Removed the BUILD_ROCKSDB cmake option, the
#ifdef BUILD_ROCKSDB guards in txdb*, and the runtime error path
that triggered when the flag was off.

CI updated: librocksdb-dev (Ubuntu), mingw-w64-x86_64-rocksdb (MSYS2),
and rocksdb (Homebrew) added to all build jobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:11:00 -07:00
sami7777 03073bd597 Rename src/signal.h to src/util_signal.h
Avoids collision with the POSIX <signal.h> system header. Pure
mechanical include-path update — no behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 18:01:53 -07:00
sami7777 674bdc7192 Add chain-DB benchmark harness (contrib/bench/)
bench-chaindb.sh times FastImportBlockFile() under each backend using a
user-supplied blk0001.dat. Wall time comes from the daemon's existing
StartupPerfLog line; peak RSS via ps sampling; datadir size via du.

Output is one CSV row per backend appended to ./bench-results.csv, plus
a stdout summary. Network is disabled during the run (-nolisten -connect=0)
so we measure only DB ingest cost.

Does not yet measure: reorg cost, network IBD speed, raw disk I/O.
LoadSnapshot path is still LevelDB-only; the harness intentionally exercises
the FastImportBlockFile rebuild instead, which works on both backends.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:52:34 -07:00
sami7777 76579e3059 Port UtxoSnapshot::DumpSnapshot to CTxDBBase iterator
DumpSnapshot reached into the LevelDB backend's internal handle via
`extern leveldb::DB *txdb`, which silently broke under -chaindb=rocksdb.
Switched to the backend-agnostic CTxDBBase::NewIterator() interface; the
function now works against either backend.

LoadSnapshot is more involved (writes directly into a fresh txleveldb/
directory) and is bundled with the eventual LevelDB retirement. Added an
IsRocksDbChainBackend() helper and an explicit guard at LoadSnapshot's
entry: refuse to load with a clear error message rather than silently
creating a leveldb tree alongside an active rocksdb chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:43:17 -07:00
sami7777 426e23d8be Add clang-format, clang-tidy, ASan/UBSan CI lanes
Format and tidy enforce only on lines changed in PRs (diff-only via
git-clang-format and clang-tidy-diff.py) — existing files keep their
current style until edited. Mass reformat deferred; .git-blame-ignore-revs
stub is in place for whenever that happens.

Sanitizer lane builds with -fsanitize=address,undefined and runs the
unit suite. continue-on-error: true initially so we can triage findings
without blocking PRs. UB categories pervasive in the Hash9 C cascade
(alignment, signed-integer-overflow, vptr) are suppressed pending
file-by-file fixes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:37:01 -07:00
sami7777 269498453e Track src/txdb-factory.cpp
This file has been built into the binary since the M1.3 chain-DB
backend split (referenced from src/CMakeLists.txt) but was never
committed. A fresh clone wouldn't build without it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:36:33 -07:00
sami7777 32330b420e Replace boost::signals2 with homegrown CSignal<>
Drops the last boost::signals2 dependency from the GUI/wallet/smessage
notification path. CSignal<> is a std::function-based fan-out signal
with explicit Connection tokens (no equivalent-bind disconnect). Same
semantics for the void-returning case; non-void variant returns the
last-connected slot's result via std::optional.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 17:36:05 -07:00
sami7777 2ba0ecf428 Cleanup: drop boost::filesystem/thread/chrono, retire dead code
Migration from boost to std-library equivalents and removal of unreachable
code paths. Touches infrastructure only — no consensus rule or wallet
serialization changes.

Dead code removed:
- IRC bootstrap (irc.cpp/h, 417 lines): orphan from pre-Tor era, no callers.
- Alert system (alert.cpp/h + sendalert RPC + Qt UI signal, ~500 lines):
  retired post-V5 fork; old peers' alert messages now hit the unknown-cmd
  default branch, logged + ignored.
- Legacy P2P handlers in main.cpp: "checkpoint" (already a no-op stub since
  V5 fork master-key removal), "checkorder"/"reply" (2010-era Receive-by-IP
  feature), plus their unused supporting structures (CRequestTracker,
  PushRequest overloads, mapRequests/cs_mapRequests, mapReuseKey).
- Unreachable RPCs clearwallettransactions and scanforalltxns (~175 lines):
  defined in rpcwallet.cpp but never registered in the dispatch table.
- Stale -alertnotify CLI help text (option was advertised but never wired).

boost::filesystem -> std::filesystem (C++17):
- 30 source files, 5 headers. namespace fs = boost::filesystem swapped to
  namespace fs = std::filesystem; boost::filesystem::ifstream/ofstream
  replaced with std::ifstream/ofstream (path-aware in C++17);
  fs::system_complete -> fs::absolute; boost::filesystem::filesystem_error
  -> std::filesystem::filesystem_error.
- Build system: dropped Boost::filesystem from link libs and Boost
  components; PCH includes updated.
- Added explicit <filesystem> includes where types were previously
  available only transitively (db.h, rpcblockchain.cpp).

boost::thread -> std::thread (12 files):
- sync.h CCriticalSection/CWaitableCriticalSection now alias
  std::recursive_mutex/std::mutex. boost::unique_lock and
  boost::condition_variable / boost::mutex::scoped_lock swapped to std
  equivalents; sync.cpp boost::thread_specific_ptr -> thread_local
  std::unique_ptr.
- init.cpp boost::thread_group rewritten as std::vector<std::thread> with
  manual join loop. boost::thread::hardware_concurrency ->
  std::thread::hardware_concurrency.
- main.cpp/wallet.cpp -blocknotify/-walletnotify shell-out threads now use
  std::thread(...).detach() — fixes a latent bug where modern boost::thread
  destructor would call std::terminate on the joinable thread.
- util.cpp NewThread now catches std::system_error.
- No interruption_point/interrupt usage anywhere — pure mechanical swap.

boost::chrono / boost::posix_time -> std::chrono (3 of 5 files):
- util.h: MilliSleep, GetTimeMillis, GetTimeMicros rewritten on std::chrono
  (system_clock for epoch math, sleep_for for delays).
- snapshotnet.cpp: sleep_for swapped.
- DoS_tests.cpp: timing harness uses steady_clock.
- Skipped: rpcdump.cpp (boost::posix_time::time_input_facet has no clean
  std::get_time equivalent) and qt/qtipcserver.cpp (locked to
  boost::posix_time by boost::interprocess::message_queue::timed_receive).

Other housekeeping:
- Dropped unnecessary "using namespace boost;" from txdb-leveldb.cpp,
  txdb-rocksdb.cpp, walletdb.cpp, db.cpp (verified no unqualified boost
  names in those TUs).
- Removed unused extern declaration for clearwallettransactions.

Build fixes for non-unity builds on MinGW64/GCC 15:
- net.cpp: dropped stale #include "irc.h".
- addrman.cpp + main.cpp: explicit <cmath> include for sqrt/pow (was
  arriving transitively via boost headers).
- rpcblockchain.cpp + init.cpp: defensive #undef STRICT/ADVISORY/PERMISSIVE
  since windows.h macros collide with the Checkpoints:: enum values when
  std headers reorder include flow.
- tor_embed_hooks.cpp: triangles_tor_check_interrupted now polls fShutdown
  instead of boost::this_thread::interruption_requested (we never used
  boost interruption — the hook was always effectively a no-op).
- snapshotnet.cpp: fs::remove error handle uses std::error_code.
- serialize.h: added <ios> for std::ios::badbit/failbit (was relying on
  transitive include via boost).

Note: unity builds currently fail on this branch due to std::byte (C++17)
colliding with COM 'byte' typedef from shlobj.h when 'using namespace std;'
from earlier files in the unity slice leaks into util.cpp's parse of
shlobj.h. Build with -DENABLE_UNITY_BUILD=OFF (the default).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 15:27:14 -07:00
Krystie 2b5471283e Remove fork chain checkpoints (2208000, 2209000) from mainnet
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled
These checkpoints correspond to abandoned fork chains and are causing
IsInitialBlockDownload() to return TRUE incorrectly. The node at
height 2,207,881 is on the main chain but the code was requiring it
to sync to checkpoint 2,209,000 which doesn't exist on mainnet.

After this change, the highest mainnet checkpoint is 2,207,000,
which the node has already passed.
2026-04-26 02:55:24 -07:00
Krystie dbde798221 Fix IsInitialBlockDownload() returning true when chain is synced but stalled
The >24h block-time check in IsInitialBlockDownload() incorrectly kept
IBD=true when the chain was fully synced but simply had no new blocks
arriving (stalled network). This prevented the stake miner from ever
proceeding past its IsInitialBlockDownload() wait loop.

Now returns false once we've passed the checkpoint height estimate,
which correctly indicates IBD is complete.

Fixes: stake miner stuck even when chain is fully synced
2026-04-26 02:34:12 -07:00
Krystie 68f5515588 Gate coinbase-height rule behind activation height 2300000
Build All Platforms / test-linux-unit (push) Has been cancelled
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-linux-qt (push) Has been cancelled
Build All Platforms / build-linux-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled
Allow historical chain sync to bypass the mandatory coinbase-height
check. Triangles blocks from the original chain do not encode block
height in the coinbase scriptSig, so unconditional enforcement causes
AcceptBlock to reject valid historical blocks during IBD.

Activation set to 2,300,000 — past the original chain's maximum height
but before any future activation point.
2026-04-25 15:52:16 -07:00
Krystie 891ad5ad25 Merge bootstrap improvements 2026-04-25 14:15:15 -07:00
Krystie c02994c836 Add checkpoint at block 2000000 2026-04-25 14:05:42 -07:00
sami7777 569ca99e66 M1.3: RocksDB chain database backend behind BUILD_ROCKSDB flag
Adds CRocksTxDB, the second concrete backend for CTxDBBase. Mirrors
CTxDB (LevelDB) one-for-one with rocksdb:: substitutions: same key
serialization (inherited from CTxDBBase), same active-batch semantics,
same LoadBlockIndex flow including the dbformat v3 chain-trust upgrade.

Build flag BUILD_ROCKSDB defaults OFF, so the existing LevelDB build is
untouched — RocksDB headers are only included when the flag is on, and
the entire .cpp file is wrapped in #ifdef BUILD_ROCKSDB.

Build system:
  * Top-level option(BUILD_ROCKSDB ... OFF)
  * find_package(RocksDB CONFIG) with pkg-config fallback
  * Conditional list(APPEND CORE_SOURCES txdb-rocksdb.cpp)
  * Conditional target_link_libraries(... RocksDB::rocksdb)

Data layout: RocksDB lives under <datadir>/rocksdb/, separate from
<datadir>/txleveldb/, so both backends can coexist for migration and
parity testing.

Acknowledged debt: LoadBlockIndex is duplicated between CTxDB and
CRocksTxDB. Will be extracted into CTxDBBase once the iterator and
batch abstractions are proven across both backends (M1.4 or later).

Validated: default-OFF build still compiles cleanly. The BUILD_ROCKSDB=ON
path is NOT compile-validated yet — RocksDB isn't installed on this dev
machine. The code is straight namespace substitution from the working
LevelDB backend; whoever first enables the flag should report any
header/API drift between rocksdb releases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 03:36:20 -07:00
93 changed files with 4155 additions and 2443 deletions
+49
View File
@@ -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
View File
@@ -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'
-11
View File
@@ -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"
]
}
}
+11
View File
@@ -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
+100 -10
View File
@@ -13,14 +13,20 @@ jobs:
runs-on: ubuntu-22.04
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Checkout (with history for submodule)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Init submodules
run: git submodule update --init --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,13 +43,70 @@ 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:
- name: Checkout (with history for submodule)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Init submodules
run: git submodule update --init --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:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- name: Checkout (with history for submodule)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Init submodules
run: git submodule update --init --recursive
shell: bash
- uses: msys2/setup-msys2@v2
with:
@@ -61,6 +124,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: |
@@ -193,7 +257,14 @@ jobs:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- name: Checkout (with history for submodule)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Init submodules
run: git submodule update --init --recursive
shell: bash
- uses: msys2/setup-msys2@v2
with:
@@ -209,6 +280,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: |
@@ -255,7 +327,13 @@ jobs:
build-linux-qt:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Checkout (with history for submodule)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Init submodules
run: git submodule update --init --recursive
- name: Set VERSION
run: |
@@ -274,7 +352,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: |
@@ -372,7 +450,13 @@ jobs:
build-linux-daemon:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Checkout (with history for submodule)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Init submodules
run: git submodule update --init --recursive
- name: Set VERSION
run: |
@@ -390,7 +474,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: |
@@ -502,7 +586,13 @@ jobs:
build-macos:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Checkout (with history for submodule)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Init submodules
run: git submodule update --init --recursive
- name: Set VERSION
run: |
@@ -517,7 +607,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: |
+100
View File
@@ -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
+10
View File
@@ -1,3 +1,6 @@
# Per-user Claude Code settings (machine-specific paths/permissions)
.claude/
# Build artifacts
*.o
*.exe
@@ -68,3 +71,10 @@ triangles.conf
*.o
src/trianglesd
src/obj/
build-bench/
build-cmake/
build-cmake-test/
build-latest/
build-rocks-probe/
build-rocksdb/
bench-results.csv
+3
View File
@@ -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
+64 -2
View File
@@ -12,7 +12,9 @@ project(Triangles
)
# ── 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)
+159
View File
@@ -0,0 +1,159 @@
# TRI v6 Development Task Queue
*Autonomous development pipeline — Krystie cycles through these continuously.*
## Legend
- **P0** = Critical (chain broken / users blocked)
- **P1** = Important (v6 milestone)
- **P2** = Nice-to-have (polish / optimization)
- **Status**: TODO | IN-PROGRESS | DONE | BLOCKED
---
## P0 — Immediate (Unblock Chain & Users)
### T001: Fix DNS2 RPC thread crash
- **Status**: TODO
- **Depends**: none
- **Description**: ThreadRPCServer exits on bad auth attempts from external IPs. Need to not kill the RPC thread on individual auth failures.
- **Files**: `src/rpc.cpp` or `src/bitcoinrpc.cpp`
- **Acceptance**: RPC stays up even with bad auth attempts; curl JSON-RPC works reliably
- **Model**: Claude Code or MiniMax M2.7
### T002: Fix DNS2 wallet 0 confirmed balance
- **Status**: TODO
- **Depends**: T001 (need reliable RPC)
- **Description**: Wallet restored from April 20 backup. Shows 11.24 TRI unconfirmed. Need to verify rescan completes and coins mature (520 confirmations) for staking.
- **Files**: wallet.dat, `src/wallet.cpp`
- **Acceptance**: Wallet shows confirmed balance after rescan + confirmations
- **Model**: Krystie (manual investigation, not subagent)
### T003: Fix seeds.txt parsing (only returns 1 address)
- **Status**: TODO
- **Depends**: none
- **Description**: HTTPS fetch of seeds.cryptographic-triangles.org/seeds.txt only returns 1 address. Possible comment parsing bug in net.cpp seed fetch logic.
- **Files**: `src/net.cpp`, `/var/www/seeds/seeds.txt`
- **Acceptance**: All 7 onion addresses returned on fetch
- **Model**: ZAI GLM-5.1
### T004: Fix Sami's PC wallet block 570 stall
- **Status**: IN-PROGRESS
- **Depends**: Windows binary build (DONE — built on sami-pc)
- **Description**: Windows Qt wallet stuck at block 570. GUI bootstrap fix committed (d0fb2dc). New binary built at E:\repos\triangles_v5\build-mingw\bin\triangles-qt.exe. Needs testing.
- **Acceptance**: Windows wallet syncs past block 570 with bootstrap
- **Model**: Krystie (manual deployment)
---
## P1 — v6 Core Milestones
### T010: Complete RocksDB runtime testing
- **Status**: TODO
- **Depends**: T001
- **Description**: RocksDB backend compiles clean but never tested with actual blockchain data. Need to: start daemon with `-rocksdb`, let it index chain, verify block lookups work, compare performance vs LevelDB.
- **Files**: `src/txdb.h`, `src/txdb.cpp`, `src/utxosnapshot.cpp`
- **Acceptance**: Daemon runs with `-rocksdb` flag, processes blocks, RPC queries return correct data
- **Model**: MiniMax M2.7
### T011: Wire UTXO snapshot P2P distribution (SnapshotNet)
- **Status**: TODO
- **Depends**: T010
- **Description**: `snapshotnet.cpp` exists but is placeholder. Need to implement: peer advertisement of snapshot availability, chunk transfer protocol, hash verification, integration with bootstrap flow.
- **Files**: `src/snapshotnet.cpp`, `src/net.cpp`, `src/utxosnapshot.cpp`
- **Acceptance**: New node can get UTXO snapshot from peers via P2P (not just HTTPS)
- **Model**: Claude Code + MiniMax M2.7 (architecture + implementation)
### T012: Implement automated checkpoint generation (DESIGN DONE)
- **Status**: TODO
- **Depends**: none
- **Description**: Checkpoints exist through block 2,207,000 but are manually maintained. Need automated checkpoint generation: every N blocks, compute checkpoint hash, push to code or external manifest.
- **Files**: `src/checkpoints.cpp`, `src/checkpoints.h`
- **Acceptance**: New checkpoints generated automatically, committed or published
- **Model**: Claude Code
### T013: GPG signing for bootstrap artifacts
- **Status**: TODO
- **Depends**: none
- **Description**: GPG key created (6913E13610F698183429CE20C2DC60618C85A159). Need to: sign every bootstrap/snapshot artifact on generation, verify signature on download, publish public key.
- **Files**: `/usr/local/bin/auto-update.sh`, `src/bootstrap.cpp`
- **Acceptance**: `gpg --verify` works on downloaded artifacts
- **Model**: ZAI GLM-5.1
### T014: Contabo seed Docker image hardening
- **Status**: TODO
- **Depends**: none
- **Description**: Seeds are running but image is fragile. Need: proper Dockerfile with version pinning, health checks, auto-restart, log shipping, and persistent volumes.
- **Files**: `/tmp/Dockerfile` on Contabo, `/tri/seed-{1..4}/`
- **Acceptance**: Seeds survive host reboot, auto-restart on crash, health check endpoint
- **Model**: ZAI GLM-5.1
### T015: Network health dashboard
- **Status**: TODO
- **Depends**: T001, T003
- **Description**: Operator-facing dashboard showing: block height per node, peer count, staking weight, chain sync status, seed health. Could be a simple web page served from DNS2.
- **Files**: New — `src/rpcblockchain.cpp` (health endpoint), frontend
- **Acceptance**: Live page showing all 7 nodes' status updated every 30s
- **Model**: MiniMax M2.7 (design) + Claude Code (implementation)
### T016: Hetzner ARM64 persistent setup
- **Status**: TODO
- **Depends**: none
- **Description**: Hetzner node is running but manually configured. Need: systemd service, auto-start on boot, bootstrap automation, monitoring.
- **Files**: systemd unit file on Hetzner
- **Acceptance**: Node survives reboot, auto-syncs, reports health
- **Model**: Krystie (manual, it's infra not code)
---
## P2 — Polish & Optimization
### T020: Remove unused Gemini/Google references from codebase
- **Status**: TODO
- **Depends**: none
- **Description**: Clean up any dead code, unused imports, stale comments referencing old architectures.
- **Model**: ZAI GLM-5.1
### T021: Comprehensive test suite
- **Status**: TODO
- **Depends**: T010
- **Description**: Expand test coverage for: UTXO snapshot load/dump, RocksDB backend, bootstrap download, seed fetch, checkpoint verification.
- **Files**: `src/test/`
- **Acceptance**: `test_triangles` passes with < 5 pre-existing failures
- **Model**: ZAI GLM-5.1 + MiniMax M2.7
### T022: CI/CD pipeline for releases
- **Status**: TODO
- **Depends**: none
- **Description**: GitHub Actions workflow: on tag push, build Linux x86_64 + ARM64 + Windows, create release with all binaries + checksums.
- **Files**: `.github/workflows/build-all.yml`
- **Acceptance**: Tag push produces release with 3 platform binaries
- **Model**: ZAI GLM-5.1
### T023: TRIdock + tri-wallet-web consolidation
- **Status**: TODO
- **Depends**: none
- **Description**: TRIdock and tri-wallet-web appear to be near-duplicates. Evaluate and either consolidate or clearly separate concerns.
- **Model**: MiniMax M2.7 (analysis)
---
## Completed
### ✅ Windows GUI bootstrap fix (d0fb2dc)
- Removed `#ifndef QT_GUI` guard so auto-bootstrap runs in GUI wallet
- Added `uiInterface.InitMessage()` for progress display
### ✅ Windows native build on sami-pc
- Built `triangles-qt.exe` (26MB) and `trianglesd.exe` via MSYS2/MinGW64
- All dependencies found natively
### ✅ RocksDB integration complete (ac9c6fb)
- CActiveTxDB wrapper, dual-backend support, compiles clean
### ✅ All nodes updated to v5.9.7.0
- DNS2, DNS3, Hetzner, Contabo seeds all running latest
### ✅ Bootstrap infrastructure live
- HTTPS at bootstrap.cryptographic-triangles.org
- Tor hidden service serving nginx on port 8085
- Seeds.txt with 7 onion nodes
+14
View File
@@ -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()
+84
View File
@@ -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
```
+217
View File
@@ -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"
+210
View File
@@ -0,0 +1,210 @@
#!/usr/bin/env bash
set -euo pipefail
# Fresh-datadir IBD smoke test for TRI.
# Goal: detect the classic "starts from zero but stalls early / loops around 570"
# failure mode, and verify that sync keeps making forward progress.
#
# Example:
# bash scripts/ibd-smoke-test.sh \
# --bin ./build/src/trianglesd \
# --bootstrap-url http://100.104.4.5:8085/triangles-bootstrap.tar.gz \
# --addnode 74.208.167.19 --addnode 194.233.88.206
BIN="${BIN:-./build/src/trianglesd}"
TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-1800}" # 30 minutes target window
POLL_SECONDS="${POLL_SECONDS:-15}"
STALL_WINDOW_SECONDS="${STALL_WINDOW_SECONDS:-180}"
BOOTSTRAP_URL="${BOOTSTRAP_URL:-}"
WORKDIR="${WORKDIR:-}"
RPC_PORT="${RPC_PORT:-19192}"
P2P_PORT="${P2P_PORT:-24193}"
MIN_EXPECTED_HEIGHT="${MIN_EXPECTED_HEIGHT:-5000}"
ALLOW_IBD="${ALLOW_IBD:-0}"
WHITELIST="${WHITELIST:-127.0.0.1}"
ADDNODES=()
usage() {
cat <<EOF
Usage: $0 [options]
Options:
--bin PATH trianglesd binary (default: $BIN)
--bootstrap-url URL optional bootstrap tar.gz URL to preload
--workdir PATH use an explicit temp workdir
--rpc-port N RPC port for test node (default: $RPC_PORT)
--p2p-port N P2P port for test node (default: $P2P_PORT)
--timeout N total test timeout seconds (default: $TIMEOUT_SECONDS)
--poll N poll interval seconds (default: $POLL_SECONDS)
--stall-window N no-progress failure window seconds (default: $STALL_WINDOW_SECONDS)
--min-height N minimum expected height/progress floor (default: $MIN_EXPECTED_HEIGHT)
--allow-ibd allow test to pass while still in IBD if progress is strong
--addnode HOST trusted peer to add (repeatable)
-h, --help show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--bin) BIN="$2"; shift 2 ;;
--bootstrap-url) BOOTSTRAP_URL="$2"; shift 2 ;;
--workdir) WORKDIR="$2"; shift 2 ;;
--rpc-port) RPC_PORT="$2"; shift 2 ;;
--p2p-port) P2P_PORT="$2"; shift 2 ;;
--timeout) TIMEOUT_SECONDS="$2"; shift 2 ;;
--poll) POLL_SECONDS="$2"; shift 2 ;;
--stall-window) STALL_WINDOW_SECONDS="$2"; shift 2 ;;
--min-height) MIN_EXPECTED_HEIGHT="$2"; shift 2 ;;
--allow-ibd) ALLOW_IBD=1; shift ;;
--addnode) ADDNODES+=("$2"); shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "unknown arg: $1" >&2; usage; exit 2 ;;
esac
done
if [[ ! -x "$BIN" ]]; then
echo "ERROR: trianglesd binary not executable: $BIN" >&2
exit 2
fi
if [[ -z "$WORKDIR" ]]; then
WORKDIR="$(mktemp -d /tmp/tri-ibd-smoke-XXXXXX)"
fi
DATADIR="$WORKDIR/datadir"
mkdir -p "$DATADIR"
RPCUSER="tri_test"
RPCPASSWORD="tri_test_$(date +%s)_$RANDOM"
CONF="$DATADIR/triangles.conf"
cat > "$CONF" <<EOF
server=1
daemon=1
staking=0
listen=1
discover=0
upnp=0
tor=0
irc=0
dnsseed=1
checkpoints=1
rpcuser=$RPCUSER
rpcpassword=$RPCPASSWORD
rpcport=$RPC_PORT
port=$P2P_PORT
maxconnections=32
whitelist=$WHITELIST
logtimestamps=1
EOF
for host in "${ADDNODES[@]}"; do
echo "addnode=$host" >> "$CONF"
done
cleanup() {
"$BIN" -datadir="$DATADIR" -conf="$CONF" stop >/dev/null 2>&1 || true
sleep 2 || true
pkill -f "$DATADIR" >/dev/null 2>&1 || true
}
trap cleanup EXIT
if [[ -n "$BOOTSTRAP_URL" ]]; then
echo "[ibd-test] downloading bootstrap: $BOOTSTRAP_URL"
curl -L --fail --max-time 1800 "$BOOTSTRAP_URL" -o "$WORKDIR/bootstrap.tar.gz"
tar xzf "$WORKDIR/bootstrap.tar.gz" -C "$DATADIR"
rm -f "$DATADIR/database/log."* "$DATADIR/txleveldb/LOCK" "$DATADIR/smsgDB/LOCK" 2>/dev/null || true
fi
echo "[ibd-test] starting node from datadir: $DATADIR"
"$BIN" -daemon -datadir="$DATADIR" -conf="$CONF" >/dev/null
sleep 6
rpc() {
local method="$1"
local params="${2:-[]}"
curl -sS --fail --user "$RPCUSER:$RPCPASSWORD" \
--data-binary "{\"jsonrpc\":\"1.0\",\"id\":\"ibd\",\"method\":\"$method\",\"params\":$params}" \
-H 'content-type: text/plain;' "http://127.0.0.1:$RPC_PORT/"
}
extract_json() {
python3 -c 'import json,sys; obj=json.load(sys.stdin); print(obj["result"])'
}
extract_field() {
local field="$1"
python3 -c 'import json,sys; obj=json.load(sys.stdin); val=obj["result"].get(sys.argv[1]); print(val if val is not None else "")' "$field"
}
start_ts=$(date +%s)
last_progress_ts=$start_ts
last_height=-1
samples=0
same_570_loops=0
best_height=0
while true; do
now=$(date +%s)
elapsed=$((now - start_ts))
if (( elapsed > TIMEOUT_SECONDS )); then
echo "FAIL: timeout after ${elapsed}s"
break
fi
if info_json="$(rpc getblockchaininfo 2>/dev/null)"; then
height=$(printf '%s' "$info_json" | extract_field blocks)
ibd=$(printf '%s' "$info_json" | extract_field initialblockdownload)
headers=$(printf '%s' "$info_json" | extract_field headers)
else
height=""
ibd=""
headers=""
fi
peers=0
if peer_json="$(rpc getconnectioncount 2>/dev/null)"; then
peers=$(printf '%s' "$peer_json" | extract_json)
fi
if [[ -n "$height" && "$height" != "$last_height" ]]; then
last_progress_ts=$now
last_height="$height"
if (( height > best_height )); then
best_height=$height
fi
fi
log_file="$DATADIR/debug.log"
if [[ -f "$log_file" ]]; then
loop_hits=$(tail -n 400 "$log_file" | grep -c 'start=571' || true)
if (( loop_hits >= 3 )); then
same_570_loops=$loop_hits
fi
fi
echo "[ibd-test] t=${elapsed}s height=${height:-?} headers=${headers:-?} ibd=${ibd:-?} peers=$peers best=$best_height"
if [[ -n "$height" ]] && (( best_height >= MIN_EXPECTED_HEIGHT )) && [[ "$ibd" == "False" || "$ibd" == "false" ]]; then
echo "PASS: left IBD and reached height $best_height"
exit 0
fi
if [[ "$ALLOW_IBD" == "1" && -n "$height" ]] && (( best_height >= MIN_EXPECTED_HEIGHT )); then
echo "PASS: strong sync progress observed (height $best_height) even though IBD remains true"
exit 0
fi
if (( now - last_progress_ts > STALL_WINDOW_SECONDS )); then
echo "FAIL: no block-height progress for $((now - last_progress_ts))s"
if (( same_570_loops > 0 )); then
echo "HINT: detected repeated start=571 loop pattern ($same_570_loops hits in recent log tail)"
fi
echo "--- debug tail ---"
tail -n 120 "$log_file" 2>/dev/null || true
exit 1
fi
((samples++)) || true
sleep "$POLL_SECONDS"
done
exit 1
+23 -8
View File
@@ -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
@@ -74,6 +74,7 @@ set(CORE_SOURCES
rpcsmessage.cpp
zmqpublishnotifier.cpp
txdb-base.cpp
txdb-factory.cpp
txdb-leveldb.cpp
utxosnapshot.cpp
snapshotnet.cpp
@@ -98,6 +99,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
@@ -115,7 +120,6 @@ target_link_libraries(triangles_common PUBLIC
leveldb_bundled
OpenSSL::SSL
OpenSSL::Crypto
Boost::filesystem
Boost::program_options
Boost::thread
Boost::chrono
@@ -145,6 +149,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 "")
@@ -202,11 +217,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>>"
+2
View File
@@ -4,6 +4,8 @@
#include "addrman.h"
#include <cmath>
using namespace std;
int CAddrInfo::GetTriedBucket(const std::vector<unsigned char> &nKey) const
-276
View File
@@ -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
View File
@@ -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
+26 -24
View File
@@ -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;
@@ -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,15 +182,17 @@ 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;
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() throw() {}
secure_allocator(const secure_allocator& a) throw() : base(a) {}
template <typename U>
@@ -199,10 +201,9 @@ struct secure_allocator : public std::allocator<T>
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);
T* p = std::allocator<T>::allocate(n);
if (p != NULL)
LockedPageManager::instance.LockRange(p, sizeof(T) * n);
return p;
@@ -226,15 +227,16 @@ 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;
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() throw() {}
zero_after_free_allocator(const zero_after_free_allocator& a) throw() : base(a) {}
template <typename U>
+1
View File
@@ -12,6 +12,7 @@
#include <openssl/bn.h>
#include <algorithm>
#include <stdexcept>
#include <vector>
+18 -13
View File
@@ -3,9 +3,10 @@
#include "bootstrap.h"
#include "utxosnapshot.h"
#include "txdb.h"
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <filesystem>
#include <fstream>
#include <boost/algorithm/string.hpp>
#include <zlib.h>
@@ -37,7 +38,7 @@
extern bool fTestNet;
namespace Checkpoints { bool IsKnownCheckpoint(int nHeight, const uint256& hash); }
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
namespace Bootstrap {
@@ -208,12 +209,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;
@@ -675,7 +677,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 +719,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 +755,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 +793,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
View File
@@ -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);
+8 -7
View File
@@ -32,14 +32,14 @@ namespace Checkpoints
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
{2000000, uint256("0xb0b02d4bb5ffa31f6f22fd042082ca0a085257af34dc2d4b31c3c8567b5574d5")},
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
{2206000, uint256("0x780ae878f8b10b6cbd51ceb2c0799c90d3551fa916be62974375071b9e36581c")},
{2203594, uint256("0x5e016ae5d1f163c6679292b717a3db467a39d24b0a315182f4783caa79c722d8")},
{2207000, uint256("0x8836d67b0f08036c4a7c26ff0a29d4461a52b6d8f552165ad9c1abec2f3cadfd")},
{2208000, uint256("0xe4a19e8a29fa7aae47f7563377af3e896fee18ed069a64e325b8c2c6c820a1be")},
{2209000, uint256("0x04c78a6fc863bed918a9364c58c64489943b2e85d84ddb1ac2fba584f390d5dc")},
};
// Published UTXO snapshot file SHA256, keyed by snapshot height.
@@ -51,7 +51,7 @@ namespace Checkpoints
// here. The corresponding (height, blockhash) must already exist in
// mapCheckpoints / mapCheckpointsTestnet.
static std::map<int, uint256> mapSnapshotHashes = {
// {2186940, uint256("0x...sha256-of-utxo-snapshot.bin...")},
{2203594, uint256("0x49b35dd01659975c4a31954f37174c6e2e8878dd0723ab306ccecd991c80f79a")},
};
static std::map<int, uint256> mapSnapshotHashesTestnet = {
@@ -70,6 +70,7 @@ namespace Checkpoints
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
{2000000, uint256("0xb0b02d4bb5ffa31f6f22fd042082ca0a085257af34dc2d4b31c3c8567b5574d5")},
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
@@ -198,7 +199,7 @@ namespace Checkpoints
bool WriteSyncCheckpoint(const uint256& hashCheckpoint)
{
CTxDB txdb;
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
txdb.TxnBegin();
if (!txdb.WriteSyncCheckpoint(hashCheckpoint))
{
@@ -224,7 +225,7 @@ namespace Checkpoints
return false;
}
CTxDB txdb;
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashPendingCheckpoint];
if (!pindexCheckpoint->IsInMainChain())
{
@@ -295,7 +296,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());
@@ -439,7 +440,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())
{
+10 -10
View File
@@ -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++;
@@ -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
View File
@@ -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 5
#define CLIENT_VERSION_REVISION 7
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
+2
View File
@@ -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;
+56
View File
@@ -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 NULL.
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;
}
+31
View File
@@ -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
+389
View File
@@ -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);
}
+121
View File
@@ -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
+3 -4
View File
@@ -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;
+5 -4
View File
@@ -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);
@@ -317,7 +318,7 @@ public:
class CAddrDB
{
private:
boost::filesystem::path pathAddr;
std::filesystem::path pathAddr;
public:
CAddrDB();
bool Write(const CAddrMan& addr);
+66 -33
View File
@@ -24,10 +24,10 @@
#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 <thread>
#include <vector>
#include <filesystem>
#include <fstream>
#include <boost/interprocess/sync/file_lock.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <openssl/crypto.h>
@@ -36,10 +36,20 @@
#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;
CClientUIInterface uiInterface;
@@ -54,7 +64,7 @@ enum Checkpoints::CPMode CheckpointsMode;
static CCriticalSection cs_DeferredStartup;
static bool fDeferredStartupRunning = false;
static boost::thread_group* pScriptCheckThreads = NULL;
static std::vector<std::thread>* pScriptCheckThreads = nullptr;
static void ThreadScriptCheck()
{
@@ -223,9 +233,10 @@ void Shutdown(void* parg)
pScriptCheckQueue->Quit();
if (pScriptCheckThreads)
{
pScriptCheckThreads->join_all();
for (std::thread& t : *pScriptCheckThreads)
if (t.joinable()) t.join();
delete pScriptCheckThreads;
pScriptCheckThreads = NULL;
pScriptCheckThreads = nullptr;
}
delete pScriptCheckQueue;
pScriptCheckQueue = NULL;
@@ -250,7 +261,7 @@ void Shutdown(void* parg)
pNotificationQueue = NULL;
}
// CTxDB().Close();
// MakeChainDB()->Close();
bitdb.Flush(false);
bitdb.Flush(true);
fs::remove(GetPidFile());
@@ -407,7 +418,7 @@ std::string HelpMessage()
//" -proxy=<ip:port> " + _("Connect through socks proxy") + "\n" +
//" -socks=<n> " + _("Select the version of socks proxy to use (4-5, default: 5)") + "\n" +
" -tor=<ip:port> " + _("Use proxy to reach tor hidden services (default: same as -proxy)") + "\n"
" -notor " + _("Disable Tor (WARNING: wallet will not start - Tor is required)") + "\n" +
" -notor " + _("Disable Tor - run in clearnet-only mode (no .onion connectivity)") + "\n" +
" -torsocks=<port> " + _("Set embedded or managed Tor SOCKS proxy port (default: 19099)") + "\n" +
" -torhiddenservice " + _("Enable the managed Tor hidden service (default: 1)") + "\n" +
" -torhsport=<port> " + _("Set embedded or managed Tor hidden service port (default: wallet listen port)") + "\n" +
@@ -469,7 +480,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" +
@@ -691,15 +701,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();
pScriptCheckThreads = new 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);
}
@@ -918,7 +928,8 @@ bool AppInit2()
// 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
// Bootstrap auto-download works for both GUI and daemon.
// GUI users get the same automatic bootstrap on fresh installs.
{
bool wantsBootstrap = GetBoolArg("-bootstrap", false);
bool noBootstrap = GetBoolArg("-nobootstrap", false);
@@ -929,6 +940,7 @@ bool AppInit2()
if (needsBootstrap && !noBootstrap && !snapshotMode) {
printf("Bootstrap: no blockchain data found — downloading automatically.\n");
printf("Bootstrap: (use -nobootstrap to skip)\n");
uiInterface.InitMessage(_("Downloading blockchain data..."));
wantsBootstrap = true;
} else if (needsBootstrap && snapshotMode && !wantsBootstrap) {
printf("Bootstrap: no blockchain data found — will fetch UTXO snapshot via P2P after network start.\n");
@@ -942,20 +954,32 @@ bool AppInit2()
std::string host = Bootstrap::DEFAULT_HOST;
std::string strError;
auto progressFn = [](int64_t bytesDownloaded, int64_t totalBytes) {
int64_t lastGuiUpdate = 0;
auto progressFn = [&lastGuiUpdate](int64_t bytesDownloaded, int64_t totalBytes) {
if (totalBytes > 0) {
printf("\rBootstrap: %lld / %lld MB (%lld%%)",
(long long)(bytesDownloaded / (1024*1024)),
(long long)(totalBytes / (1024*1024)),
(long long)((bytesDownloaded * 100) / totalBytes));
fflush(stdout);
// Update GUI status bar every ~1 MB
int64_t now = GetTimeMillis();
if (now - lastGuiUpdate > 1000) {
lastGuiUpdate = now;
std::string msg = strprintf("Downloading blockchain: %lld / %lld MB (%lld%%)",
(long long)(bytesDownloaded / (1024*1024)),
(long long)(totalBytes / (1024*1024)),
(long long)((bytesDownloaded * 100) / totalBytes));
uiInterface.InitMessage(msg);
}
}
};
// 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());
@@ -989,16 +1013,16 @@ bool AppInit2()
strprintf("host=%s success=%d utxo_snapshot=%d", host.c_str(), success, triedUtxoSnapshot));
}
} // end bootstrap scope
#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..."));
@@ -1024,22 +1048,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..."));
@@ -1051,7 +1075,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..."));
@@ -1224,7 +1248,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) &&
@@ -1322,6 +1346,15 @@ bool AppInit2()
#ifdef USE_UPNP
fUseUPnP = false;
#endif
} else if (GetBoolArg("-notor", false)) {
// -notor: user explicitly disabled Tor. Allow the daemon to start
// in clearnet-only mode (useful for diagnostics, benchmarking, and
// recovery). .onion connectivity will not be available.
printf("NOTICE: Tor disabled via -notor. Running in clearnet-only mode.\n");
printf(" .onion connections will NOT be available.\n");
SetReachable(NET_IPV4, true);
SetReachable(NET_IPV6, true);
SetReachable(NET_TOR, false);
} else {
std::string torError = CTorEmbedded::GetInstance()->GetStartupError();
if (torError.empty())
-405
View File
@@ -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
-12
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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;
}
+7 -7
View File
@@ -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;
@@ -105,20 +103,22 @@ 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;
/** 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();
+2 -2
View File
@@ -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;
@@ -177,7 +177,7 @@ public:
/* Wallet status (encrypted, locked) changed.
* Note: Called without locks held.
*/
boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged;
CSignal<void(CCryptoKeyStore*)> NotifyStatusChanged;
};
#endif
+111 -167
View File
@@ -3,9 +3,10 @@
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "alert.h"
#include "checkpoints.h"
#include "db.h"
#include <cmath>
#include "txdb.h"
#include "net.h"
#include "init.h"
@@ -23,13 +24,13 @@
#include <algorithm>
#include <deque>
#include <boost/algorithm/string/replace.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <filesystem>
#include <fstream>
using namespace std;
using namespace boost;
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
//
// Global state
@@ -931,7 +932,7 @@ bool CTransaction::ReadFromDisk(CTxDBBase& txdb, COutPoint prevout)
bool CTransaction::ReadFromDisk(COutPoint prevout)
{
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
CTxIndex txindex;
return ReadFromDisk(txdb, prevout, txindex);
}
@@ -1064,7 +1065,7 @@ int CMerkleTx::SetMerkleBranch(const CBlock* pblock)
{
// Load the block this tx is in
CTxIndex txindex;
if (!CTxDB("r").ReadTxIndex(GetHash(), txindex))
if (!MakeChainDB("r")->ReadTxIndex(GetHash(), txindex))
return 0;
if (!blockTmp.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos))
return 0;
@@ -1487,7 +1488,7 @@ bool CMerkleTx::AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs)
bool CMerkleTx::AcceptToMemoryPool()
{
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
return AcceptToMemoryPool(txdb);
}
@@ -1515,7 +1516,7 @@ bool CWalletTx::AcceptWalletTransaction(CTxDBBase& txdb, bool fCheckInputs)
bool CWalletTx::AcceptWalletTransaction()
{
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
return AcceptWalletTransaction(txdb);
}
@@ -1548,7 +1549,7 @@ bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock)
return true;
}
}
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
CTxIndex txindex;
if (tx.ReadFromDisk(txdb, COutPoint(hash, 0), txindex))
{
@@ -1837,6 +1838,12 @@ int GetNumBlocksOfPeers()
bool IsInitialBlockDownload()
{
// Bootstrap escape hatch: when the network has stalled and every node
// thinks it is in IBD because the tip is older than 24h, -forcestaking
// lets a single operator mint the first block to unstick the chain.
if (GetBoolArg("-forcestaking", false) && pindexBest != NULL &&
nBestHeight >= Checkpoints::GetTotalBlocksEstimate())
return false;
if (pindexBest == NULL || nBestHeight < Checkpoints::GetTotalBlocksEstimate())
return true;
static int64_t nLastUpdate;
@@ -1846,8 +1853,11 @@ bool IsInitialBlockDownload()
pindexLastBest = pindexBest;
nLastUpdate = GetTime();
}
return (GetTime() - nLastUpdate < 10 &&
pindexBest->GetBlockTime() < GetTime() - 24 * 60 * 60);
// IBD is complete once we've passed the checkpoint height estimate.
// The previous >24h block-time check incorrectly kept IBD true when the
// chain was synced but simply stalled (no new blocks arriving), which
// prevented the stake miner from ever proceeding.
return false;
}
void static InvalidChainFound(CBlockIndex* pindexNew)
@@ -1855,7 +1865,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew)
if (pindexNew->nChainTrust > nBestInvalidTrust)
{
nBestInvalidTrust = pindexNew->nChainTrust;
CTxDB().WriteBestInvalidTrust(CBigNum(nBestInvalidTrust));
MakeChainDB()->WriteBestInvalidTrust(CBigNum(nBestInvalidTrust));
uiInterface.NotifyBlocksChanged();
}
@@ -1972,8 +1982,12 @@ bool CTransaction::FetchInputs(CTxDBBase& txdb, const MapPrevTx& mapPendingUtxos
}
else
{
// Backfill to UTXO DB for future lookups
txdb.WriteUtxo(prevout.hash, prevout.n, backfill);
// Backfill to UTXO DB for future lookups. Skip the
// write when the handle is read-only (wallet/mempool
// callers open "r"); ConnectBlock will persist it
// later via the writable chain handle.
if (!txdb.IsReadOnly())
txdb.WriteUtxo(prevout.hash, prevout.n, backfill);
inputsRet[prevout] = backfill;
continue;
}
@@ -3038,7 +3052,7 @@ bool CBlock::SetBestChain(CTxDBBase& txdb, CBlockIndex* pindexNew)
if (!fIsInitialDownload && !strCmd.empty())
{
boost::replace_all(strCmd, "%s", hashBestChain.GetHex());
boost::thread t(runCommand, strCmd); // thread runs free
std::thread(runCommand, strCmd).detach(); // thread runs free
}
#ifdef ENABLE_ZMQ
@@ -3168,7 +3182,7 @@ bool CBlock::GetCoinAge(uint64_t& nCoinAge) const
{
nCoinAge = 0;
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
for (const CTransaction& tx : vtx)
{
uint64_t nTxCoinAge;
@@ -3247,7 +3261,7 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const u
pindexNew->phashBlock = &((*mi).first);
// Write to disk block index
CTxDB txdb;
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
if (!txdb.TxnBegin())
return false;
txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
@@ -3476,11 +3490,17 @@ bool CBlock::AcceptBlock()
// - Hardcoded checkpoints already guarantee chain integrity
// - The persisted hashSyncCheckpoint in LevelDB blocks IBD from progressing
// Enforce rule that the coinbase starts with serialized block height
CScript expect = CScript() << nHeight;
if (vtx[0].vin[0].scriptSig.size() < expect.size() ||
!std::equal(expect.begin(), expect.end(), vtx[0].vin[0].scriptSig.begin()))
return DoS(100, error("AcceptBlock() : block height mismatch in coinbase"));
// Legacy Triangles blocks were created before mandatory coinbase-height enforcement.
// Do NOT enforce this rule against historical chain data during recovery/import.
static const int COINBASE_HEIGHT_ENFORCEMENT_HEIGHT = 2300000;
if (nHeight >= COINBASE_HEIGHT_ENFORCEMENT_HEIGHT)
{
CScript expect = CScript() << nHeight;
if (vtx[0].vin[0].scriptSig.size() < expect.size() ||
!std::equal(expect.begin(), expect.end(), vtx[0].vin[0].scriptSig.begin()))
return DoS(100, error("AcceptBlock() : block height mismatch in coinbase"));
}
// Write block to history file
if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK, CLIENT_VERSION)))
@@ -3888,7 +3908,7 @@ bool LoadBlockIndex(bool fAllowNew)
//
// Load block index
//
CTxDB txdb("cr+");
auto txdb_holder = MakeChainDB("cr+"); CTxDBBase& txdb = *txdb_holder;
if (!txdb.LoadBlockIndex())
return false;
@@ -4172,7 +4192,7 @@ bool FastImportBlockFile()
LOCK(cs_main);
CAutoFile blkdat(fileIn, SER_DISK, CLIENT_VERSION);
CTxDB txdb;
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
txdb.TxnBegin();
unsigned int nPos = 0;
@@ -4383,17 +4403,8 @@ bool FastImportBlockFile()
return nLoaded > 0;
}
//////////////////////////////////////////////////////////////////////////////
//
// CAlert
//
extern map<uint256, CAlert> mapAlerts;
extern CCriticalSection cs_mapAlerts;
string GetWarnings(string strFor)
{
int nPriority = 0;
string strStatusBar;
string strRPC;
@@ -4402,33 +4413,11 @@ string GetWarnings(string strFor)
// Misc warnings like out of disk space and clock is wrong
if (strMiscWarning != "")
{
nPriority = 1000;
strStatusBar = strMiscWarning;
}
// triangles: if detected invalid checkpoint enter safe mode
if (Checkpoints::hashInvalidCheckpoint != 0)
{
nPriority = 3000;
strStatusBar = strRPC = _("WARNING: Invalid checkpoint found! Displayed transactions may not be correct! You may need to upgrade, or notify developers.");
}
// Alerts
{
LOCK(cs_mapAlerts);
for (auto& item : mapAlerts)
{
const CAlert& alert = item.second;
if (alert.AppliesToMe() && alert.nPriority > nPriority)
{
nPriority = alert.nPriority;
strStatusBar = alert.strStatusBar;
if (nPriority > 1000)
strRPC = strStatusBar; // triangles: safe mode for high alert
}
}
}
if (strFor == "statusbar")
return strStatusBar;
@@ -4485,7 +4474,6 @@ unsigned char pchMessageStart[4] = { 0x70, 0x35, 0x22, 0x05 };
bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
{
static map<CService, CPubKey> mapReuseKey;
RandAddSeedPerfmon();
if (fDebug)
printf("received: %s (%" PRIszu " bytes)\n", strCommand.c_str(), vRecv.size());
@@ -4620,13 +4608,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
fIBD ? "+getheaders" : "", nBestHeight, pfrom->addr.ToString().c_str());
}
// Relay alerts
{
LOCK(cs_mapAlerts);
for (auto& item : mapAlerts)
item.second.RelayTo(pfrom);
}
// Sync checkpoint relay disabled (master key removed in V5 fork).
// Relaying stale checkpoints causes IBD nodes to request far-future blocks.
@@ -4774,7 +4755,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
printf("IBD-DIAG: inv received: %d blocks, %d tx from %s (our height=%d)\n",
nBlockInv, nTxInv, pfrom->addr.ToString().c_str(), nBestHeight);
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
int nNew = 0, nAlready = 0, nAboveBest = 0;
int nFirstInvHeight = -1, nLastInvHeight = -1;
for (unsigned int nInv = 0; nInv < vInv.size(); nInv++)
@@ -4975,13 +4956,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
}
}
}
else if (strCommand == "checkpoint")
{
// Sync checkpoint system disabled (master key removed in V5 fork).
// Ignore checkpoint messages — processing them during IBD causes the
// node to request a single far-future block instead of syncing sequentially.
}
else if (strCommand == "getheaders")
{
CBlockLocator locator;
@@ -5120,7 +5094,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
vector<uint256> vWorkQueue;
vector<uint256> vEraseQueue;
CDataStream vMsg(vRecv);
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
CTransaction tx;
vRecv >> tx;
@@ -5493,53 +5467,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
}
else if (strCommand == "checkorder")
{
uint256 hashReply;
vRecv >> hashReply;
if (!GetBoolArg("-allowreceivebyip"))
{
pfrom->PushMessage("reply", hashReply, (int)2, string(""));
return true;
}
CWalletTx order;
vRecv >> order;
/// we have a chance to check the order here
// Keep giving the same key to the same ip until they use it
if (!mapReuseKey.count(pfrom->addr))
pwalletMain->GetKeyFromPool(mapReuseKey[pfrom->addr], true);
// Send back approval of order and pubkey to use
CScript scriptPubKey;
scriptPubKey << mapReuseKey[pfrom->addr] << OP_CHECKSIG;
pfrom->PushMessage("reply", hashReply, (int)0, scriptPubKey);
}
else if (strCommand == "reply")
{
uint256 hashReply;
vRecv >> hashReply;
CRequestTracker tracker;
{
LOCK(pfrom->cs_mapRequests);
map<uint256, CRequestTracker>::iterator mi = pfrom->mapRequests.find(hashReply);
if (mi != pfrom->mapRequests.end())
{
tracker = (*mi).second;
pfrom->mapRequests.erase(mi);
}
}
if (!tracker.IsNull())
tracker.fn(tracker.param1, vRecv);
}
else if (strCommand == "ping")
{
if (pfrom->nVersion > BIP0031_VERSION)
@@ -5585,37 +5512,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
}
else if (strCommand == "alert")
{
CAlert alert;
vRecv >> alert;
uint256 alertHash = alert.GetHash();
if (pfrom->setKnown.count(alertHash) == 0)
{
if (alert.ProcessAlert())
{
// Relay
pfrom->setKnown.insert(alertHash);
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
alert.RelayTo(pnode);
}
}
else {
// Small DoS penalty so peers that send us lots of
// duplicate/expired/invalid-signature/whatever alerts
// eventually get banned.
// This isn't a Misbehaving(100) (immediate ban) because the
// peer might be an older or different implementation with
// a different signature key, etc.
pfrom->Misbehaving(10);
}
}
}
else if (strCommand == "getwalletaddr")
{
// Peer is requesting our TRI receiving address for onion resolution.
@@ -6098,24 +5994,55 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
(int)pto->mapAskFor.size());
nLastStallLog = GetTime();
}
// Use the walk-forward progress point if available, to avoid
// restarting from pindexBest (which hits the CBlockLocator
// exponential gap and starts the walk-forward from scratch).
pto->pindexLastGetBlocksBegin = NULL;
if (nHighestInvWalk > nBestHeight && hashHighestInvWalk != 0 &&
mapBlockIndex.count(hashHighestInvWalk))
// During IBD, avoid falling back to legacy getblocks recovery
// anchored at pindexBest or a stale inv walk point. That path can
// repeatedly resolve the locator to the same low common ancestor
// on a weak peer set, which looks like a sync "freeze" near an
// early height even though the real bug is the recovery loop.
// Keep stall recovery header-driven instead so the planner tip
// advances from the newest known header state.
if (IsInitialBlockDownload())
{
pto->PushGetBlocks(mapBlockIndex[hashHighestInvWalk], uint256(0));
printf("SYNC-DIAG: stall re-request from walk=%d (not best=%d)\n",
nHighestInvWalk, nBestHeight);
} else {
pto->PushGetBlocks(pindexBest, uint256(0));
pto->pindexLastGetHeadersBegin = NULL;
uint256 hashLocatorTip = hashBestHeaderSync;
if (hashLocatorTip == 0 && nHighestInvWalk > nBestHeight &&
hashHighestInvWalk != 0 && mapBlockIndex.count(hashHighestInvWalk))
{
hashLocatorTip = hashHighestInvWalk;
}
unsigned int nRefilled = RequestHeaderSyncRefillAllPeers(
hashLocatorTip,
0,
"stall-recovery");
unsigned int nQueued = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
printf("SYNC-DIAG: stall recovery used headers-first path (locator=%s, refillPeers=%u, queued=%u)\n",
hashLocatorTip.ToString().substr(0,20).c_str(),
nRefilled,
nQueued);
}
else
{
// Outside IBD, preserve the older walk-forward getblocks
// behavior since we're no longer building out a header planner.
pto->pindexLastGetBlocksBegin = NULL;
if (nHighestInvWalk > nBestHeight && hashHighestInvWalk != 0 &&
mapBlockIndex.count(hashHighestInvWalk))
{
pto->PushGetBlocks(mapBlockIndex[hashHighestInvWalk], uint256(0));
printf("SYNC-DIAG: stall re-request from walk=%d (not best=%d)\n",
nHighestInvWalk, nBestHeight);
}
else
{
pto->PushGetBlocks(pindexBest, uint256(0));
}
pto->pindexLastGetHeadersBegin = NULL;
pto->PushGetHeaders(pindexBest, uint256(0));
}
// Also send getheaders during stall to restart the header planner.
// Without this, a drained header cache stays empty because only
// getblocks is sent on stall, which can't refill mapHeaderSync.
pto->pindexLastGetHeadersBegin = NULL;
pto->PushGetHeaders(pindexBest, uint256(0));
nLastBlockReceived = GetTime();
}
}
@@ -6143,6 +6070,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
const unsigned int nInFlight = CountHeaderSyncInFlight();
static int64_t nLastHeaderPlannerControl = 0;
static int64_t nLastHeaderWatchdog = 0;
static int64_t nLastBlockPlannerControl = 0;
if (nLastNewHeaderTime == 0)
nLastNewHeaderTime = nNowSec;
@@ -6180,6 +6108,22 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
if (nNowSec - pto->nLastIbdHeaderRequest >= nMinInterval)
RequestHeaderSyncRefill(pto, hashBestHeaderSync, nMinInterval, "heartbeat");
// Keep the block planner alive even when no new headers arrive and
// no blocks are being accepted. Without this periodic kick, the
// redundant-request and timeout logic inside QueueHeaderSyncBlocksParallel()
// only runs on header arrivals or block acceptance, so IBD can park
// indefinitely behind one missing frontier block.
if (nNowSec - nLastBlockPlannerControl >= HEADER_SYNC_CONTROL_INTERVAL_SECONDS &&
hashBestHeaderSync != 0 &&
nPlannerDepth > 0)
{
const unsigned int nRequeued = QueueHeaderSyncBlocksParallel(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;
}
}
//
@@ -6203,7 +6147,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
vector<CInv> vGetData;
int64_t nNow = GetTime() * 1000000;
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
// During IBD, send larger getdata batches since PoS blocks are small
// and the bottleneck is round-trip latency, not bandwidth.
unsigned int nGetDataBatchSize = IsInitialBlockDownload() ? 4000 : 1000;
+4 -3
View File
@@ -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
@@ -387,6 +387,7 @@ void StakeMiner(CWallet *pwallet)
RenameThread("Triangles-miner");
bool fTryToSync = true;
bool fForceStaking = GetBoolArg("-forcestaking", false);
while (true)
{
@@ -401,7 +402,7 @@ void StakeMiner(CWallet *pwallet)
return;
}
while (vNodes.empty() || IsInitialBlockDownload())
while (!fForceStaking && (vNodes.empty() || IsInitialBlockDownload()))
{
nLastCoinStakeSearchInterval = 0;
fTryToSync = true;
@@ -410,7 +411,7 @@ void StakeMiner(CWallet *pwallet)
return;
}
if (fTryToSync)
if (fTryToSync && !fForceStaking)
{
fTryToSync = false;
if (vNodes.size() < 2 || nBestHeight < GetNumBlocksOfPeers())
+3 -8
View File
@@ -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"
@@ -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;
}
}
}
-68
View File
@@ -18,7 +18,6 @@
#include "protocol.h"
#include "addrman.h"
class CRequestTracker;
class CNode;
class CBlockIndex;
bool IsInitialBlockDownload();
@@ -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
{
@@ -268,8 +248,6 @@ protected:
int nMisbehavior;
public:
std::map<uint256, CRequestTracker> mapRequests;
CCriticalSection cs_mapRequests;
uint256 hashContinue;
CBlockIndex* pindexLastGetBlocksBegin;
uint256 hashLastGetBlocksEnd;
@@ -690,52 +668,6 @@ public:
}
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);
bool IsSubscribed(unsigned int nChannel);
+9 -8
View File
@@ -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;
}
};
+14 -7
View File
@@ -1,16 +1,23 @@
#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"},
// 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"},
{NULL}
};
+5 -37
View File
@@ -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();
}
+4 -1
View File
@@ -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
+22 -22
View File
@@ -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
View File
@@ -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
View File
@@ -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)));
}
+3
View File
@@ -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 */
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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>";
+1 -1
View File
@@ -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)
+11 -8
View File
@@ -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
+3
View File
@@ -8,6 +8,7 @@
#include <QMutex>
#include "allocators.h" /* for SecureString */
#include "../util_signal.h"
class OptionsModel;
class AddressTableModel;
@@ -157,6 +158,8 @@ private:
void unsubscribeFromCoreSignals();
bool checkBalanceChanged();
CSignalConnections m_core_signal_connections;
public slots:
/* Wallet status might have changed */
+123 -11
View File
@@ -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;
@@ -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)
@@ -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.");
@@ -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));
-66
View File
@@ -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"
@@ -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)
{
+2 -2
View File
@@ -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,7 +548,7 @@ 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");
+14 -12
View File
@@ -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();
-178
View File
@@ -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;
}
Submodule
+1
Submodule src/secp256k1 added at 1a53f4961f
+1
View File
@@ -11,6 +11,7 @@
#include <map>
#include <set>
#include <cassert>
#include <ios>
#include <limits>
#include <stdint.h>
#include <cstring>
+269 -113
View File
@@ -41,8 +41,6 @@ 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>
@@ -53,6 +51,7 @@ Notes:
#include "base58.h"
#include "crypto_ecdh.h"
#include "db.h"
#include "init.h" // pwalletMain
#include "txdb.h"
@@ -60,6 +59,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 +82,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 +99,163 @@ uint32_t nPeerIdCounter = 1;
CCriticalSection cs_smsg;
CCriticalSection cs_smsgDB;
leveldb::DB *smsgDB = NULL;
rocksdb::DB *smsgDB = NULL;
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;
@@ -390,9 +545,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 +555,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 +583,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 +621,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;
pendingBatch.clear();
if (!status.ok())
{
@@ -504,6 +642,7 @@ bool SecMsgDB::TxnAbort()
{
delete activeBatch;
activeBatch = NULL;
pendingBatch.clear();
return true;
};
@@ -531,12 +670,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 +708,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 +745,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 +778,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 +819,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 +853,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 +887,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 +900,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 +1023,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 +1031,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
@@ -2238,7 +2380,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 +2419,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")
@@ -2472,7 +2614,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 +2694,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 +2762,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 +3261,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 +3351,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;
@@ -3539,7 +3681,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 +3776,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()
@@ -3837,7 +3983,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 +4196,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.
+20 -10
View File
@@ -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;
@@ -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);
+7 -7
View File
@@ -15,8 +15,8 @@
#include <openssl/sha.h>
#include <boost/filesystem/fstream.hpp>
#include <boost/thread.hpp>
#include <filesystem>
#include <thread>
#include <algorithm>
#include <atomic>
@@ -27,7 +27,7 @@
#include <mutex>
#include <vector>
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
extern std::vector<CNode*> vNodes;
extern CCriticalSection cs_vNodes;
@@ -353,7 +353,7 @@ bool TryFetchSnapshot(const fs::path& dataDir, int timeoutSec, std::string& strE
g_fetch.success = false;
// Drop bad file so we don't trick later loaders.
CloseDest();
boost::system::error_code ec;
std::error_code ec;
fs::remove(g_fetch.destPath, ec);
}
g_fetch.finished = true;
@@ -362,7 +362,7 @@ bool TryFetchSnapshot(const fs::path& dataDir, int timeoutSec, std::string& strE
}
}
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
bool ok;
@@ -374,7 +374,7 @@ bool TryFetchSnapshot(const fs::path& dataDir, int timeoutSec, std::string& strE
strError = strprintf("timeout after %d seconds (totalSize=%" PRId64 ", chunks=%" PRIszu ")",
timeoutSec, g_fetch.totalSize, g_fetch.received.size());
CloseDest();
boost::system::error_code ec;
std::error_code ec;
fs::remove(g_fetch.destPath, ec);
}
ok = g_fetch.success;
@@ -420,7 +420,7 @@ static bool ScanLocalSnapshot()
uint256 expectedHash;
if (!Checkpoints::GetSnapshotHash(snapHeight, expectedHash)) return false;
boost::system::error_code ec;
std::error_code ec;
int64_t sz = (int64_t)fs::file_size(g_localPath, ec);
if (ec) return false;
+2 -2
View File
@@ -7,7 +7,7 @@
#include "uint256.h"
#include "serialize.h"
#include <boost/filesystem.hpp>
#include <filesystem>
#include <string>
#include <vector>
@@ -47,7 +47,7 @@ struct AvailableSnapshot
//
// 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 boost::filesystem::path& dataDir,
bool TryFetchSnapshot(const std::filesystem::path& dataDir,
int timeoutSec,
std::string& strError);
+3 -3
View File
@@ -48,9 +48,9 @@ private:
typedef std::vector< std::pair<void*, CLockLocation> > LockStack;
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,7 +74,7 @@ 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());
+14 -19
View File
@@ -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 */
typedef std::recursive_mutex CCriticalSection;
/** 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 */
typedef std::mutex CWaitableCriticalSection;
#ifdef DEBUG_LOCKORDER
void EnterCritical(const char* pszName, const char* pszFile, int nLine, void* cs, bool fTry = false);
@@ -36,7 +31,7 @@ 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();
+7 -9
View File
@@ -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");
+1 -1
View File
@@ -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
+6 -7
View File
@@ -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);
}
+10 -10
View File
@@ -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>
@@ -60,12 +60,12 @@ extern CWallet* pwalletMain;
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 +573,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 +616,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(
@@ -1218,7 +1218,7 @@ bool CTorV3Manager::InitializeTor()
torDataDir = torV3Config.torDataDirectory;
// Create tor data directory
boost::filesystem::create_directories(torDataDir);
std::filesystem::create_directories(torDataDir);
torEnabled = true;
+4 -4
View File
@@ -12,8 +12,8 @@
#include "../util.h"
#include "../net.h"
#include <boost/filesystem.hpp>
#include <boost/thread/thread.hpp>
#include <filesystem>
#include <thread>
#include <fstream>
#include <cstring>
#include <vector>
@@ -35,7 +35,7 @@ extern "C" {
}
#endif
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
// Singleton
CTorEmbedded* CTorEmbedded::instance = nullptr;
@@ -152,7 +152,7 @@ bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
running.store(true);
// Launch Tor on a dedicated thread (tor_run_main blocks)
boost::thread torThread(TorThreadFunc, argv);
std::thread torThread(TorThreadFunc, argv);
torThread.detach();
// Wait for SOCKS port to become available (up to 60s)
+29 -5
View File
@@ -35,7 +35,7 @@
#include <unistd.h>
#endif
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
static std::string ReadTailLines(const fs::path& filePath, size_t maxLines)
{
@@ -259,10 +259,34 @@ bool CTorProcess::WriteTorrc()
// SOCKS proxy for wallet connections
torrc << "SocksPort " << socksPort << "\n";
// Data directory for Tor state
fs::path torStateDir = dataPath / "state";
fs::create_directories(torStateDir);
torrc << "DataDirectory " << torStateDir.string() << "\n";
// Data directory for Tor state.
// Use the tor_data directory itself as DataDirectory so that Tor creates
// its internal 'state' FILE at <tor_data>/state. Older wallet builds
// erroneously created a subdirectory called 'state' and pointed
// DataDirectory at it; newer Tor versions (0.4.9+) reject that because
// they expect to write a plain file called 'state' inside DataDirectory.
//
// Recovery: if 'state' exists as a directory, move its contents up and
// remove it so that Tor can create its state file in the normal location.
{
fs::path badStateDir = dataPath / "state";
if (fs::exists(badStateDir) && fs::is_directory(badStateDir)) {
// Migrate any files inside the bad 'state/' directory up to dataPath
try {
for (auto& entry : fs::directory_iterator(badStateDir)) {
fs::path dest = dataPath / entry.path().filename();
if (!fs::exists(dest)) {
fs::rename(entry.path(), dest);
}
}
fs::remove(badStateDir);
printf("Auto-recovered: removed legacy 'state' directory from %s\n", dataPath.string().c_str());
} catch (const fs::filesystem_error& e) {
printf("WARNING: Could not auto-recover tor_data/state directory: %s\n", e.what());
}
}
}
torrc << "DataDirectory " << dataPath.string() << "\n";
// Persistent Tor log for post-mortem debugging on user machines.
fs::path torLogPath = dataPath / "tor.log";
+7 -8
View File
@@ -5,9 +5,8 @@
#include "tor_embed_hooks.h"
#include "util.h"
#include <boost/filesystem.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
#include <mutex>
#include <thread>
#include <memory>
#include <string>
@@ -25,13 +24,13 @@ const char* triangles_onion_service_directory()
int triangles_tor_check_interrupted()
{
return boost::this_thread::interruption_requested() ? 1 : 0;
return fShutdown ? 1 : 0;
}
static boost::mutex g_torInitializing;
static std::mutex g_torInitializing;
static std::unique_ptr<boost::unique_lock<boost::mutex> > g_torUninitialized(
new boost::unique_lock<boost::mutex>(g_torInitializing));
static std::unique_ptr<std::unique_lock<std::mutex> > g_torUninitialized(
new std::unique_lock<std::mutex>(g_torInitializing));
void triangles_tor_set_initialized()
{
@@ -40,5 +39,5 @@ void triangles_tor_set_initialized()
void triangles_tor_wait_initialized()
{
boost::unique_lock<boost::mutex> checking(g_torInitializing);
std::unique_lock<std::mutex> checking(g_torInitializing);
}
+90 -56
View File
@@ -13,18 +13,20 @@
#include "main.h"
#include "net.h"
#include "notificationqueue.h"
#include "util_signal.h"
#undef printf
#include <boost/asio.hpp>
#include <boost/asio/ip/v6_only.hpp>
#include <boost/bind.hpp>
#include <boost/filesystem.hpp>
#include <filesystem>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/filesystem/fstream.hpp>
#include <fstream>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <memory>
#include <list>
@@ -34,7 +36,7 @@ using namespace std;
using namespace boost;
using namespace boost::asio;
using namespace json_spirit;
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
void ThreadRPCServer2(void* parg);
@@ -320,13 +322,13 @@ static const CRPCCommand vRPCCommands[] =
{ "invalidateblock", &invalidateblock, false, false },
{ "reconsiderblock", &reconsiderblock, false, false },
{ "recalculatesupply", &recalculatesupply, false, false },
{ "auditsignatures", &auditsignatures, true, false },
{ "dumputxoset", &dumputxoset, false, false },
{ "reservebalance", &reservebalance, false, true},
{ "checkwallet", &checkwallet, false, true},
{ "repairwallet", &repairwallet, false, true},
{ "resendtx", &resendtx, false, true},
{ "makekeypair", &makekeypair, false, true},
{ "sendalert", &sendalert, false, false},
{ "smsgenable", &smsgenable, false, false},
{ "smsgdisable", &smsgdisable, false, false},
@@ -546,10 +548,17 @@ int ReadHTTP(std::basic_istream<char>& stream, map<string, string>& mapHeadersRe
bool HTTPAuthorized(map<string, string>& mapHeaders)
{
string strAuth = mapHeaders["authorization"];
if (strAuth.substr(0,6) != "Basic ")
if (strAuth.size() < 6 || strAuth.substr(0,6) != "Basic ")
return false;
string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64);
string strUserPass = DecodeBase64(strUserPass64);
if (strUserPass64.empty())
return false;
string strUserPass;
try {
strUserPass = DecodeBase64(strUserPass64);
} catch (const std::exception&) {
return false;
}
return TimingResistantEqual(strUserPass, strRPCUserColonPass);
}
@@ -786,44 +795,61 @@ static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol,
{
vnThreadsRunning[THREAD_RPCLISTENER]++;
// Immediately start accepting new connections, except when we're cancelled or our socket is closed.
if (error != asio::error::operation_aborted
&& acceptor->is_open())
RPCListen(acceptor, context, fUseSSL);
try {
// Immediately start accepting new connections, except when we're cancelled or our socket is closed.
if (error != asio::error::operation_aborted
&& acceptor->is_open())
RPCListen(acceptor, context, fUseSSL);
AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn);
AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn);
if (error)
{
if (error != asio::error::operation_aborted)
printf("RPC accept error from %s: %s (%d)\n",
tcp_conn ? tcp_conn->peer.address().to_string().c_str() : "unknown peer",
error.message().c_str(),
error.value());
if (error)
{
if (error != asio::error::operation_aborted)
printf("RPC accept error from %s: %s (%d)\n",
tcp_conn ? tcp_conn->peer.address().to_string().c_str() : "unknown peer",
error.message().c_str(),
error.value());
delete conn;
vnThreadsRunning[THREAD_RPCLISTENER]--;
return;
}
// Restrict callers by IP. It is important to
// do this before starting client thread, to filter out
// certain DoS and misbehaving clients.
else if (tcp_conn
&& !ClientAllowed(tcp_conn->peer.address()))
{
// Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake.
try {
if (!fUseSSL)
conn->stream() << HTTPReply(HTTP_FORBIDDEN, "", false) << std::flush;
} catch (const std::exception& e) {
printf("RPC error sending 403 to %s: %s\n",
tcp_conn->peer.address().to_string().c_str(), e.what());
}
delete conn;
vnThreadsRunning[THREAD_RPCLISTENER]--;
return;
}
// start HTTP client thread
else if (!NewThread(ThreadRPCServer3, conn)) {
printf("Failed to create RPC server client thread\n");
delete conn;
}
vnThreadsRunning[THREAD_RPCLISTENER]--;
} catch (std::exception& e) {
PrintException(&e, "RPCAcceptHandler()");
delete conn;
vnThreadsRunning[THREAD_RPCLISTENER]--;
return;
}
// Restrict callers by IP. It is important to
// do this before starting client thread, to filter out
// certain DoS and misbehaving clients.
else if (tcp_conn
&& !ClientAllowed(tcp_conn->peer.address()))
{
// Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake.
if (!fUseSSL)
conn->stream() << HTTPReply(HTTP_FORBIDDEN, "", false) << std::flush;
} catch (...) {
PrintException(NULL, "RPCAcceptHandler()");
delete conn;
vnThreadsRunning[THREAD_RPCLISTENER]--;
}
// start HTTP client thread
else if (!NewThread(ThreadRPCServer3, conn)) {
printf("Failed to create RPC server client thread\n");
delete conn;
}
vnThreadsRunning[THREAD_RPCLISTENER]--;
}
void ThreadRPCServer2(void* parg)
@@ -848,9 +874,7 @@ void ThreadRPCServer2(void* parg)
"rpcpassword=%s\n"
"(you do not need to remember this password)\n"
"The username and password MUST NOT be the same.\n"
"If the file does not exist, create it with owner-readable-only file permissions.\n"
"It is also recommended to set alertnotify so you are notified of problems;\n"
"for example: alertnotify=echo %%s | mail -s \"Triangles Alert\" admin@foo.com\n"),
"If the file does not exist, create it with owner-readable-only file permissions.\n"),
strWhatAmI.c_str(),
GetConfigFile().string().c_str(),
EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()),
@@ -889,7 +913,7 @@ void ThreadRPCServer2(void* parg)
boost::system::error_code v6_only_error;
boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(io_service));
boost::signals2::signal<void ()> StopRequests;
CSignal<void()> StopRequests;
bool fListening = false;
std::string strerr;
@@ -905,10 +929,15 @@ void ThreadRPCServer2(void* parg)
acceptor->listen(socket_base::max_listen_connections);
RPCListen(acceptor, context, fUseSSL);
// Cancel outstanding listen-requests for this acceptor when shutting down
StopRequests.connect(signals2::slot<void ()>(
static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get())
.track(acceptor));
// Cancel outstanding listen-requests for this acceptor when shutting down.
// weak_ptr emulates signals2's .track(): if the acceptor has already been
// released by the time StopRequests fires, the slot is a no-op.
{
boost::weak_ptr<ip::tcp::acceptor> weak_acceptor(acceptor);
StopRequests.connect([weak_acceptor]() {
if (auto a = weak_acceptor.lock()) a->close();
});
}
fListening = true;
}
@@ -931,10 +960,13 @@ void ThreadRPCServer2(void* parg)
acceptor->listen(socket_base::max_listen_connections);
RPCListen(acceptor, context, fUseSSL);
// Cancel outstanding listen-requests for this acceptor when shutting down
StopRequests.connect(signals2::slot<void ()>(
static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get())
.track(acceptor));
// See note above on weak_ptr-based .track() emulation.
{
boost::weak_ptr<ip::tcp::acceptor> weak_acceptor(acceptor);
StopRequests.connect([weak_acceptor]() {
if (auto a = weak_acceptor.lock()) a->close();
});
}
fListening = true;
}
@@ -1125,6 +1157,7 @@ void ThreadRPCServer3(void* parg)
AcceptedConnection *conn = (AcceptedConnection *) parg;
bool fRun = true;
try {
while (true)
{
if (fShutdown || !fRun)
@@ -1244,6 +1277,13 @@ void ThreadRPCServer3(void* parg)
}
}
} // end try
catch (std::exception& e) {
PrintException(&e, "ThreadRPCServer3()");
} catch (...) {
PrintException(NULL, "ThreadRPCServer3()");
}
delete conn;
{
LOCK(cs_THREAD_RPCHANDLER);
@@ -1397,12 +1437,6 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector<std::stri
if (strMethod == "walletpassphrase" && n > 2) ConvertTo<bool>(params[2]);
if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]);
if (strMethod == "sendalert" && n > 2) ConvertTo<boost::int64_t>(params[2]);
if (strMethod == "sendalert" && n > 3) ConvertTo<boost::int64_t>(params[3]);
if (strMethod == "sendalert" && n > 4) ConvertTo<boost::int64_t>(params[4]);
if (strMethod == "sendalert" && n > 5) ConvertTo<boost::int64_t>(params[5]);
if (strMethod == "sendalert" && n > 6) ConvertTo<boost::int64_t>(params[6]);
if (strMethod == "sendmany" && n > 1) ConvertTo<Object>(params[1]);
if (strMethod == "sendmany" && n > 2) ConvertTo<boost::int64_t>(params[2]);
if (strMethod == "reservebalance" && n > 0) ConvertTo<bool>(params[0]);
+1 -6
View File
@@ -157,8 +157,6 @@ extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fH
extern json_spirit::Value dumpprivkey(const json_spirit::Array& params, bool fHelp); // in rpcdump.cpp
extern json_spirit::Value importprivkey(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value sendalert(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getsubsidy(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getmininginfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getstakinginfo(const json_spirit::Array& params, bool fHelp);
@@ -228,16 +226,13 @@ extern json_spirit::Value getchaintips(const json_spirit::Array& params, bool fH
extern json_spirit::Value invalidateblock(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value reconsiderblock(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value recalculatesupply(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value auditsignatures(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value dumputxoset(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getaddressbalance(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getaddressutxos(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getaddresstxids(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value clearwallettransactions(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value smsgenable(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value smsgdisable(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value smsglocalkeys(const json_spirit::Array& params, bool fHelp);
+4 -1
View File
@@ -141,6 +141,10 @@ public:
bool HaveUtxo(const uint256& hash, unsigned int n);
int64_t SumUtxoValues(int& nCount);
// Range scans use this directly (e.g. UtxoSnapshot::DumpSnapshot iterating
// the "u" keyspace). The iterator yields raw serialized key/value bytes.
virtual std::unique_ptr<CTxDBIteratorBase> NewIterator() const = 0;
protected:
bool fReadOnly = false;
@@ -149,7 +153,6 @@ protected:
virtual bool WriteRaw(const std::string& key, const std::string& value) = 0;
virtual bool EraseRaw(const std::string& key) = 0;
virtual bool ExistsRaw(const std::string& key) const = 0;
virtual std::unique_ptr<CTxDBIteratorBase> NewIterator() const = 0;
// Templated Read/Write/Erase/Exists are non-virtual (templates can't be
// virtual in C++) — they serialize and dispatch to the byte-level virtuals.
+73
View File
@@ -0,0 +1,73 @@
// 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 "txdb.h"
#include "util.h"
#include <filesystem>
#include <stdexcept>
#include <string>
namespace fs = std::filesystem;
namespace {
// Pick the backend once per process. -chaindb is a startup flag; switching at
// runtime would require reopening every CTxDB instance, which the codebase
// doesn't currently support. We cache the resolved choice so subsequent
// MakeChainDB calls don't re-parse the argument.
enum class ChainDbKind { LevelDB, RocksDB };
ChainDbKind ResolveChainDbKind()
{
static const ChainDbKind kKind = []() {
std::string s = GetArg("-chaindb", std::string("leveldb"));
for (auto& c : s) c = std::tolower(static_cast<unsigned char>(c));
if (s == "leveldb")
return ChainDbKind::LevelDB;
if (s == "rocksdb")
return ChainDbKind::RocksDB;
throw std::runtime_error(
"-chaindb=" + s + " is not a recognized backend. "
"Valid values: leveldb, rocksdb.");
}();
return kKind;
}
} // anonymous namespace
std::unique_ptr<CTxDBBase> MakeChainDB(const char* pszMode)
{
switch (ResolveChainDbKind()) {
case ChainDbKind::LevelDB:
return std::unique_ptr<CTxDBBase>(new CTxDB(pszMode));
case ChainDbKind::RocksDB:
return std::unique_ptr<CTxDBBase>(new CRocksTxDB(pszMode));
}
// Unreachable — ResolveChainDbKind throws on bad input.
return nullptr;
}
bool IsRocksDbChainBackend()
{
return ResolveChainDbKind() == ChainDbKind::RocksDB;
}
std::filesystem::path GetChainDataDir()
{
switch (ResolveChainDbKind()) {
case ChainDbKind::LevelDB: return GetDataDir() / "txleveldb";
case ChainDbKind::RocksDB: return GetDataDir() / "rocksdb";
}
return GetDataDir() / "txleveldb"; // unreachable
}
void WipeChainDataDir()
{
fs::path p = GetChainDataDir();
if (fs::exists(p))
fs::remove_all(p);
}
+3 -4
View File
@@ -5,9 +5,9 @@
#include <map>
#include <filesystem>
#include <boost/version.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <leveldb/env.h>
#include <leveldb/cache.h>
@@ -24,8 +24,7 @@
#include "main.h"
using namespace std;
using namespace boost;
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
leveldb::DB *txdb; // global pointer for LevelDB object instance
+710
View File
@@ -0,0 +1,710 @@
// 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 "txdb-rocksdb.h"
#include <map>
#include <filesystem>
#include <boost/version.hpp>
#include <rocksdb/cache.h>
#include <rocksdb/filter_policy.h>
#include <rocksdb/iterator.h>
#include <rocksdb/slice.h>
#include <rocksdb/table.h>
#include <rocksdb/write_batch.h>
#include "kernel.h"
#include "checkpoints.h"
#include "txdb.h"
#include "util.h"
#include "ui_interface.h"
#include "addressindex.h"
#include "main.h"
using namespace std;
namespace fs = std::filesystem;
// Global pointer for the RocksDB instance, shared across CRocksTxDB instances
// the same way the LevelDB backend shares its txdb singleton.
static rocksdb::DB* g_rocksdb = nullptr;
namespace {
// rocksdb::DB::Open shipped a raw DB** overload for years; newer releases
// (Homebrew's macOS rocksdb 10.x) replaced it with std::unique_ptr<DB>*.
// SFINAE picks whichever overload the linked rocksdb actually has —
// `int` is preferred over `long`, so when DB** exists, the first overload
// wins; otherwise the unique_ptr fallback runs.
template<typename T>
inline auto OpenRocksDBImpl(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 OpenRocksDBImpl(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 OpenRocksDB(const rocksdb::Options& opts,
const std::string& path,
rocksdb::DB** dbptr)
{
return OpenRocksDBImpl(opts, path, dbptr, 0);
}
} // anonymous namespace
static rocksdb::Options GetRocksOptions()
{
rocksdb::Options opts;
opts.create_if_missing = false;
opts.compression = rocksdb::kSnappyCompression;
opts.max_open_files = 1000;
opts.write_buffer_size = 64 * 1048576;
opts.IncreaseParallelism(); // Multi-threaded compaction.
opts.OptimizeLevelStyleCompaction(); // Sensible defaults for a LSM workload.
rocksdb::BlockBasedTableOptions table_opts;
int nCacheSizeMB = GetArg("-dbcache", 2048);
table_opts.block_cache = rocksdb::NewLRUCache(static_cast<size_t>(nCacheSizeMB) * 1048576);
table_opts.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10, false));
opts.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_opts));
return opts;
}
static void open_rocksdb(rocksdb::Options& options, bool fRemoveOld = false)
{
fs::path directory = GetDataDir() / "rocksdb";
if (fRemoveOld) {
fs::remove_all(directory);
}
fs::create_directory(directory);
printf("Opening RocksDB in %s\n", directory.string().c_str());
rocksdb::Status status = OpenRocksDB(options, directory.string(), &g_rocksdb);
if (!status.ok()) {
throw runtime_error(strprintf("open_rocksdb(): error opening database: %s",
status.ToString().c_str()));
}
}
CRocksTxDB::CRocksTxDB(const char* pszMode)
: pdb(nullptr), activeBatch(nullptr), nVersion(0)
{
assert(pszMode);
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
if (g_rocksdb) {
pdb = g_rocksdb;
return;
}
bool fCreate = strchr(pszMode, 'c');
options = GetRocksOptions();
options.create_if_missing = fCreate;
open_rocksdb(options);
pdb = g_rocksdb;
if (Exists(string("version")))
{
ReadVersion(nVersion);
printf("RocksDB transaction index version is %d\n", nVersion);
if (nVersion < DATABASE_VERSION)
{
printf("Required index version is %d, removing old RocksDB database\n",
DATABASE_VERSION);
delete g_rocksdb;
g_rocksdb = pdb = nullptr;
delete activeBatch;
activeBatch = nullptr;
open_rocksdb(options, true);
pdb = g_rocksdb;
bool fTmp = fReadOnly;
fReadOnly = false;
WriteVersion(DATABASE_VERSION);
fReadOnly = fTmp;
}
}
else if (fCreate)
{
bool fTmp = fReadOnly;
fReadOnly = false;
WriteVersion(DATABASE_VERSION);
fReadOnly = fTmp;
}
printf("Opened RocksDB successfully\n");
}
CRocksTxDB::~CRocksTxDB()
{
delete activeBatch;
}
void CRocksTxDB::Close()
{
delete g_rocksdb;
g_rocksdb = pdb = nullptr;
delete activeBatch;
activeBatch = nullptr;
}
bool CRocksTxDB::TxnBegin()
{
if (activeBatch)
return true;
activeBatch = new rocksdb::WriteBatch();
pendingBatch.clear();
return true;
}
bool CRocksTxDB::TxnCommit()
{
assert(activeBatch);
rocksdb::Status status = pdb->Write(rocksdb::WriteOptions(), activeBatch);
delete activeBatch;
activeBatch = nullptr;
pendingBatch.clear();
if (!status.ok()) {
printf("ERROR: RocksDB batch commit failure: %s\n", status.ToString().c_str());
printf("ERROR: This may indicate disk full, corruption, or permissions issue.\n");
printf("ERROR: Chain state may be inconsistent - immediate investigation required!\n");
return false;
}
return true;
}
bool CRocksTxDB::TxnAbort()
{
delete activeBatch;
activeBatch = nullptr;
pendingBatch.clear();
return true;
}
namespace {
class CRocksDBIterator final : public CTxDBIteratorBase {
public:
explicit CRocksDBIterator(rocksdb::Iterator* pit) : pit(pit) {}
~CRocksDBIterator() override { delete pit; }
void Seek(const std::string& key) override { pit->Seek(key); }
bool Valid() const override { return pit->Valid(); }
void Next() override { pit->Next(); }
std::string KeyStr() const override { return pit->key().ToString(); }
std::string ValueStr() const override { return pit->value().ToString(); }
private:
rocksdb::Iterator* pit;
};
} // anonymous namespace
bool CRocksTxDB::ScanBatch(const std::string& key, std::string* value, bool* deleted) const
{
assert(activeBatch);
*deleted = false;
auto it = pendingBatch.find(key);
if (it == pendingBatch.end())
return false;
if (!it->second.has_value()) {
*deleted = true;
return true;
}
*value = *it->second;
return true;
}
bool CRocksTxDB::ReadRaw(const std::string& key, std::string& value) const
{
bool readFromDb = true;
if (activeBatch) {
bool deleted = false;
readFromDb = ScanBatch(key, &value, &deleted) == false;
if (deleted)
return false;
}
if (readFromDb) {
rocksdb::Status status = pdb->Get(rocksdb::ReadOptions(), key, &value);
if (!status.ok()) {
if (status.IsNotFound())
return false;
printf("RocksDB read failure: %s\n", status.ToString().c_str());
return false;
}
}
return true;
}
bool CRocksTxDB::WriteRaw(const std::string& key, const std::string& value)
{
if (activeBatch) {
activeBatch->Put(key, value);
pendingBatch[key] = value;
return true;
}
rocksdb::Status status = pdb->Put(rocksdb::WriteOptions(), key, value);
if (!status.ok()) {
printf("RocksDB write failure: %s\n", status.ToString().c_str());
return false;
}
return true;
}
bool CRocksTxDB::EraseRaw(const std::string& key)
{
if (!pdb)
return false;
if (activeBatch) {
activeBatch->Delete(key);
pendingBatch[key] = std::nullopt;
return true;
}
rocksdb::Status status = pdb->Delete(rocksdb::WriteOptions(), key);
return (status.ok() || status.IsNotFound());
}
bool CRocksTxDB::ExistsRaw(const std::string& key) const
{
std::string unused;
if (activeBatch) {
bool deleted = false;
if (ScanBatch(key, &unused, &deleted) && !deleted)
return true;
}
rocksdb::Status status = pdb->Get(rocksdb::ReadOptions(), key, &unused);
return status.IsNotFound() == false;
}
std::unique_ptr<CTxDBIteratorBase> CRocksTxDB::NewIterator() const
{
return std::unique_ptr<CTxDBIteratorBase>(
new CRocksDBIterator(pdb->NewIterator(rocksdb::ReadOptions())));
}
// ─── LoadBlockIndex ─────────────────────────────────────────────────────────
// Mirrors CTxDB::LoadBlockIndex with rocksdb:: substitutions. The dbformat
// upgrade path is preserved verbatim because a freshly-imported RocksDB may
// have been migrated from a v1 LevelDB and still need the chain-trust pass.
//
// This duplication is acknowledged debt — CTxDBBase will absorb LoadBlockIndex
// into the base class in a later phase once the iterator/batch abstractions
// have proven stable across both backends.
// ────────────────────────────────────────────────────────────────────────────
static CBlockIndex *InsertBlockIndexRocks(uint256 hash)
{
if (hash == 0)
return nullptr;
auto mi = mapBlockIndex.find(hash);
if (mi != mapBlockIndex.end())
return mi->second;
CBlockIndex* pindexNew = new CBlockIndex();
if (!pindexNew)
throw runtime_error("LoadBlockIndex(): new CBlockIndex failed");
mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
pindexNew->phashBlock = &mi->first;
return pindexNew;
}
bool CRocksTxDB::LoadBlockIndex()
{
if (mapBlockIndex.size() > 0) {
return true;
}
int nDbFormat = 1;
ReadDbFormat(nDbFormat);
CDiskBlockIndex::fSerializeChainTrust = (nDbFormat >= 2);
if (CDiskBlockIndex::fSerializeChainTrust)
printf("LoadBlockIndex(): RocksDB format v%d - nChainTrust persisted\n", nDbFormat);
else
printf("LoadBlockIndex(): RocksDB format v%d - will recalculate nChainTrust\n", nDbFormat);
int64_t nPhaseStart = GetTimeMillis();
int64_t nTotalStart = nPhaseStart;
rocksdb::Iterator* iterator = pdb->NewIterator(rocksdb::ReadOptions());
CDataStream ssStartKey(SER_DISK, CLIENT_VERSION);
ssStartKey << make_pair(string("blockindex"), uint256(0));
iterator->Seek(ssStartKey.str());
int nBlocksLoaded = 0;
while (iterator->Valid())
{
if (++nBlocksLoaded % 100000 == 0)
{
std::string strMsg = strprintf(_("Loading block index... (%d blocks)"), nBlocksLoaded);
uiInterface.InitMessage(strMsg);
}
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.write(iterator->key().data(), iterator->key().size());
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
ssValue.write(iterator->value().data(), iterator->value().size());
string strType;
ssKey >> strType;
if (fRequestShutdown || strType != "blockindex")
break;
CDiskBlockIndex diskindex;
ssValue >> diskindex;
uint256 blockHash = diskindex.GetBlockHash();
CBlockIndex* pindexNew = InsertBlockIndexRocks(blockHash);
pindexNew->pprev = InsertBlockIndexRocks(diskindex.hashPrev);
pindexNew->pnext = InsertBlockIndexRocks(diskindex.hashNext);
pindexNew->nFile = diskindex.nFile;
pindexNew->nBlockPos = diskindex.nBlockPos;
pindexNew->nHeight = diskindex.nHeight;
pindexNew->nMint = diskindex.nMint;
pindexNew->nMoneySupply = diskindex.nMoneySupply;
pindexNew->nFlags = diskindex.nFlags;
pindexNew->nStakeModifier = diskindex.nStakeModifier;
pindexNew->prevoutStake = diskindex.prevoutStake;
pindexNew->nStakeTime = diskindex.nStakeTime;
pindexNew->hashProofOfStake = diskindex.hashProofOfStake;
pindexNew->nVersion = diskindex.nVersion;
pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot;
pindexNew->nTime = diskindex.nTime;
pindexNew->nBits = diskindex.nBits;
pindexNew->nNonce = diskindex.nNonce;
pindexNew->nChainTrust = diskindex.nChainTrust;
if (pindexGenesisBlock == nullptr && blockHash == (!fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet))
pindexGenesisBlock = pindexNew;
if (!pindexNew->CheckIndex()) {
delete iterator;
return error("LoadBlockIndex(): CheckIndex failed at %d", pindexNew->nHeight);
}
iterator->Next();
}
delete iterator;
printf("STARTUP-PERF: block_index_deserialize %" PRId64 "ms blocks=%d\n",
GetTimeMillis() - nPhaseStart, nBlocksLoaded);
if (fRequestShutdown)
return true;
nPhaseStart = GetTimeMillis();
bool fNeedChainTrustRecalc = !CDiskBlockIndex::fSerializeChainTrust;
if (fNeedChainTrustRecalc)
{
uiInterface.InitMessage(_("Calculating chain trust (one-time upgrade)..."));
vector<pair<int, CBlockIndex*> > vSortedByHeight;
vSortedByHeight.reserve(mapBlockIndex.size());
for (const auto& item : mapBlockIndex)
vSortedByHeight.push_back(make_pair(item.second->nHeight, item.second));
sort(vSortedByHeight.begin(), vSortedByHeight.end());
int nLastCheckpointHeight = Checkpoints::GetTotalBlocksEstimate();
int nProgressInterval = std::max((int)vSortedByHeight.size() / 20, 1);
int nCount = 0;
for (const auto& item : vSortedByHeight)
{
CBlockIndex* pindex = item.second;
pindex->nChainTrust = (pindex->pprev ? pindex->pprev->nChainTrust : 0)
+ pindex->GetBlockTrust();
if (pindex->nHeight >= nLastCheckpointHeight)
{
pindex->nStakeModifierChecksum = GetStakeModifierChecksum(pindex);
if (!CheckStakeModifierCheckpoints(pindex->nHeight, pindex->nStakeModifierChecksum))
return error("LoadBlockIndex(): Failed stake modifier checkpoint h=%d, mod=0x%016"PRIx64,
pindex->nHeight, pindex->nStakeModifier);
}
if (++nCount % nProgressInterval == 0)
{
std::string strMsg = strprintf(_("Calculating chain trust... (%d%%)"),
nCount * 100 / vSortedByHeight.size());
uiInterface.InitMessage(strMsg);
}
}
printf("LoadBlockIndex(): upgrading RocksDB to format v3...\n");
uiInterface.InitMessage(_("Upgrading block index..."));
CDiskBlockIndex::fSerializeChainTrust = true;
rocksdb::WriteBatch batch;
nCount = 0;
for (const auto& item : vSortedByHeight)
{
CBlockIndex* pindex = item.second;
CDiskBlockIndex diskindex(pindex);
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey << make_pair(string("blockindex"), *pindex->phashBlock);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
ssValue << diskindex;
batch.Put(ssKey.str(), ssValue.str());
if (++nCount % 100000 == 0)
{
pdb->Write(rocksdb::WriteOptions(), &batch);
batch.Clear();
printf("LoadBlockIndex(): upgraded %d / %d entries\n",
nCount, (int)vSortedByHeight.size());
}
}
CDataStream ssFmtKey(SER_DISK, CLIENT_VERSION);
ssFmtKey << string("dbformat");
CDataStream ssFmtValue(SER_DISK, CLIENT_VERSION);
ssFmtValue << (int)3;
batch.Put(ssFmtKey.str(), ssFmtValue.str());
rocksdb::Status status = pdb->Write(rocksdb::WriteOptions(), &batch);
if (!status.ok())
return error("LoadBlockIndex(): failed to write upgraded block index: %s",
status.ToString().c_str());
printf("LoadBlockIndex(): RocksDB upgraded to format v3 (%d entries)\n", nCount);
}
else
{
int nLastCheckpointHeight = Checkpoints::GetTotalBlocksEstimate();
bool fNeedModifierCheck = false;
for (const auto& item : mapBlockIndex)
{
if (item.second->nHeight >= nLastCheckpointHeight)
{
fNeedModifierCheck = true;
break;
}
}
if (fNeedModifierCheck)
{
vector<pair<int, CBlockIndex*> > vAboveCheckpoint;
for (const auto& item : mapBlockIndex)
if (item.second->nHeight >= nLastCheckpointHeight)
vAboveCheckpoint.push_back(make_pair(item.second->nHeight, item.second));
sort(vAboveCheckpoint.begin(), vAboveCheckpoint.end());
for (const auto& item : vAboveCheckpoint)
{
CBlockIndex* pindex = item.second;
pindex->nStakeModifierChecksum = GetStakeModifierChecksum(pindex);
if (!CheckStakeModifierCheckpoints(pindex->nHeight, pindex->nStakeModifierChecksum))
return error("LoadBlockIndex(): Failed stake modifier checkpoint h=%d, mod=0x%016"PRIx64,
pindex->nHeight, pindex->nStakeModifier);
}
}
}
printf("STARTUP-PERF: chain_trust_and_modifiers %" PRId64 "ms\n",
GetTimeMillis() - nPhaseStart);
if (nDbFormat < 3)
{
WriteDbFormat(3);
printf("LoadBlockIndex(): bumped RocksDB dbformat to v3\n");
}
nPhaseStart = GetTimeMillis();
if (!ReadHashBestChain(hashBestChain))
{
if (pindexGenesisBlock == nullptr)
return true;
return error("LoadBlockIndex(): hashBestChain not loaded");
}
if (!mapBlockIndex.count(hashBestChain))
return error("LoadBlockIndex(): hashBestChain not found in the block index");
pindexBest = mapBlockIndex[hashBestChain];
nBestHeight = pindexBest->nHeight;
nBestChainTrust = pindexBest->nChainTrust;
printf("STARTUP-PERF: best_chain %" PRId64 "ms\n", GetTimeMillis() - nPhaseStart);
nPhaseStart = GetTimeMillis();
{
int nStakeSeenDepth = 500;
CBlockIndex* pindex = pindexBest;
int nLoaded = 0;
while (pindex && nLoaded < nStakeSeenDepth)
{
if (pindex->IsProofOfStake())
setStakeSeen.insert(make_pair(pindex->prevoutStake, pindex->nStakeTime));
pindex = pindex->pprev;
nLoaded++;
}
printf("LoadBlockIndex(): populated setStakeSeen with %d entries (last %d blocks)\n",
(int)setStakeSeen.size(), nLoaded);
}
printf("STARTUP-PERF: stake_seen %" PRId64 "ms\n", GetTimeMillis() - nPhaseStart);
printf("LoadBlockIndex(): hashBestChain=%s height=%d trust=%s date=%s\n",
hashBestChain.ToString().substr(0,20).c_str(), nBestHeight,
CBigNum(nBestChainTrust).ToString().c_str(),
DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str());
{
CBlockIndex* pindexBetter = nullptr;
for (const auto& item : mapBlockIndex)
{
CBlockIndex* pindex = item.second;
if (pindex == pindexBest)
continue;
if (pindex->nChainTrust > nBestChainTrust)
{
pindexBetter = pindex;
break;
}
if (pindex->nChainTrust == nBestChainTrust &&
pindex->GetBlockHash() < pindexBest->GetBlockHash())
{
if (!pindexBetter || pindex->GetBlockHash() < pindexBetter->GetBlockHash())
pindexBetter = pindex;
}
}
if (pindexBetter)
{
printf("LoadBlockIndex(): better chain tip %s at %d (trust %s vs %s)\n",
pindexBetter->GetBlockHash().ToString().substr(0,20).c_str(),
pindexBetter->nHeight,
CBigNum(pindexBetter->nChainTrust).ToString().c_str(),
CBigNum(nBestChainTrust).ToString().c_str());
CBlock block;
if (block.ReadFromDisk(pindexBetter))
{
CRocksTxDB txdb2;
if (block.SetBestChain(txdb2, pindexBetter))
{
hashBestChain = pindexBetter->GetBlockHash();
pindexBest = pindexBetter;
nBestHeight = pindexBetter->nHeight;
nBestChainTrust = pindexBetter->nChainTrust;
printf("LoadBlockIndex(): switched to better chain tip\n");
}
}
}
}
if (!ReadSyncCheckpoint(Checkpoints::hashSyncCheckpoint))
printf("LoadBlockIndex(): no sync checkpoint in DB, using default\n");
else
printf("LoadBlockIndex(): synchronized checkpoint %s\n",
Checkpoints::hashSyncCheckpoint.ToString().c_str());
if (!mapBlockIndex.count(Checkpoints::hashSyncCheckpoint))
{
printf("LoadBlockIndex(): sync checkpoint not in index, resetting to genesis\n");
Checkpoints::hashSyncCheckpoint = (!fTestNet ? hashGenesisBlockOfficial
: hashGenesisBlockTestNet);
}
CBigNum bnBestInvalidTrust;
ReadBestInvalidTrust(bnBestInvalidTrust);
nBestInvalidTrust = bnBestInvalidTrust.getuint256();
nPhaseStart = GetTimeMillis();
int nCheckLevel = GetArg("-checklevel", 1);
int nCheckDepth = GetArg("-checkblocks", 50);
if (nCheckDepth == 0)
nCheckDepth = 1000000000;
if (nCheckDepth > nBestHeight)
nCheckDepth = nBestHeight;
printf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel);
CBlockIndex* pindexFork = nullptr;
map<pair<unsigned int, unsigned int>, CBlockIndex*> mapBlockPos;
for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev)
{
if (fRequestShutdown || pindex->nHeight < nBestHeight - nCheckDepth)
break;
CBlock block;
if (!block.ReadFromDisk(pindex))
return error("LoadBlockIndex(): block.ReadFromDisk failed");
if (nCheckLevel > 0 && !block.CheckBlock(true, true, (nCheckLevel > 6)))
{
printf("LoadBlockIndex(): bad block at %d, hash=%s\n",
pindex->nHeight, pindex->GetBlockHash().ToString().c_str());
pindexFork = pindex->pprev;
}
if (nCheckLevel > 1)
{
pair<unsigned int, unsigned int> pos = make_pair(pindex->nFile, pindex->nBlockPos);
mapBlockPos[pos] = pindex;
for (const CTransaction &tx : block.vtx)
{
uint256 hashTx = tx.GetHash();
CTxIndex txindex;
if (ReadTxIndex(hashTx, txindex))
{
if (nCheckLevel > 2 || pindex->nFile != txindex.pos.nFile
|| pindex->nBlockPos != txindex.pos.nBlockPos)
{
CTransaction txFound;
if (!txFound.ReadFromDisk(txindex.pos))
{
printf("LoadBlockIndex(): cannot read mislocated transaction %s\n",
hashTx.ToString().c_str());
pindexFork = pindex->pprev;
}
else if (txFound.GetHash() != hashTx)
{
printf("LoadBlockIndex(): invalid tx position for %s\n",
hashTx.ToString().c_str());
pindexFork = pindex->pprev;
}
}
if (nCheckLevel > 3 && !tx.IsCoinBase())
{
for (const CTxIn &txin : tx.vin)
{
if (HaveUtxo(txin.prevout.hash, txin.prevout.n))
{
printf("LoadBlockIndex(): spent input still in UTXO set: %s:%i in %s\n",
txin.prevout.hash.ToString().c_str(), txin.prevout.n,
hashTx.ToString().c_str());
pindexFork = pindex->pprev;
}
}
}
}
}
}
}
if (pindexFork && !fRequestShutdown)
{
printf("LoadBlockIndex(): moving best chain pointer back to block %d\n",
pindexFork->nHeight);
CBlock block;
if (!block.ReadFromDisk(pindexFork))
return error("LoadBlockIndex(): block.ReadFromDisk failed");
CRocksTxDB txdb;
block.SetBestChain(txdb, pindexFork);
}
printf("STARTUP-PERF: verify_blocks %" PRId64 "ms depth=%d level=%d\n",
GetTimeMillis() - nPhaseStart, nCheckDepth, nCheckLevel);
printf("STARTUP-PERF: load_block_index_total %" PRId64 "ms\n",
GetTimeMillis() - nTotalStart);
return true;
}
+64
View File
@@ -0,0 +1,64 @@
// 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_TXDB_ROCKSDB_H
#define TRIANGLES_TXDB_ROCKSDB_H
#include "txdb-base.h"
#include <map>
#include <optional>
#include <string>
#include <rocksdb/db.h>
#include <rocksdb/options.h>
#include <rocksdb/write_batch.h>
// RocksDB backend for the chain database.
//
// Mirrors CTxDB (LevelDB) for byte-level compatibility. CTxDBBase owns all
// key serialization, so keys produced by this backend are bit-identical to
// the LevelDB backend. That property is what lets the M1.4 dual-backend
// parity harness verify equivalence.
//
// Data lives under <datadir>/rocksdb/, separate from <datadir>/txleveldb/,
// so both backends can coexist for migration and side-by-side testing.
class CRocksTxDB final : public CTxDBBase
{
public:
CRocksTxDB(const char* pszMode = "r+");
~CRocksTxDB() override;
void Close() override;
bool TxnBegin() override;
bool TxnCommit() override;
bool TxnAbort() override;
bool LoadBlockIndex() override;
protected:
bool ReadRaw(const std::string& key, std::string& value) const override;
bool WriteRaw(const std::string& key, const std::string& value) override;
bool EraseRaw(const std::string& key) override;
bool ExistsRaw(const std::string& key) const override;
std::unique_ptr<CTxDBIteratorBase> NewIterator() const override;
private:
rocksdb::DB* pdb; // Points to the global instance.
rocksdb::WriteBatch* activeBatch; // When non-NULL, writes/deletes go here.
rocksdb::Options options;
int nVersion;
// Parallel record of every pending write (value) or delete (nullopt) on
// activeBatch. Used by ScanBatch to answer "is this key already 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;
bool ScanBatch(const std::string& key, std::string* value, bool* deleted) const;
};
#endif // TRIANGLES_TXDB_ROCKSDB_H
+30 -1
View File
@@ -1,11 +1,40 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2012 The Bitcoin developers
// Copyright (c) 2026 The Triangles developers
// Distributed under the MIT/X11 software license, see the accompanying
// file license.txt or http://www.opensource.org/licenses/mit-license.php.
#ifndef TRIANGLES_TXDB_H
#define TRIANGLES_TXDB_H
#include "txdb-base.h"
#include "txdb-leveldb.h"
#include "txdb-rocksdb.h"
#endif // TRIANGLES_TXDB_H
#include <filesystem>
#include <memory>
// Factory: returns a chain-database handle whose concrete backend is chosen
// by the -chaindb command-line argument:
//
// -chaindb=leveldb (default — pending Phase-4 retirement)
// -chaindb=rocksdb
//
// Callers receive a CTxDBBase*, so the rest of the codebase stays
// backend-agnostic. Mode strings ("r", "r+", "cr+") match the pre-existing
// CTxDB constructor convention.
std::unique_ptr<CTxDBBase> MakeChainDB(const char* pszMode = "r+");
// True when the configured chain-DB backend is RocksDB.
bool IsRocksDbChainBackend();
// On-disk directory of the chain DB for the configured backend, e.g.
// <datadir>/txleveldb (LevelDB) or <datadir>/rocksdb (RocksDB).
std::filesystem::path GetChainDataDir();
// Remove the chain DB directory for the configured backend. Callers that
// need a fresh DB (-reindex, snapshot load) must invoke this BEFORE
// MakeChainDB() opens the global handle for the first time.
void WipeChainDataDir();
#endif // TRIANGLES_TXDB_H
+15 -22
View File
@@ -6,11 +6,9 @@
#ifndef TRIANGLES_UI_INTERFACE_H
#define TRIANGLES_UI_INTERFACE_H
#include <boost/signals2/last_value.hpp>
#include <boost/signals2/signal.hpp>
#include <boost/bind/bind.hpp>
using namespace boost::placeholders;
#include "util_signal.h"
#include <optional>
#include <string>
#include <stdint.h>
@@ -65,46 +63,41 @@ public:
};
/** Show message box. */
boost::signals2::signal<void (const std::string& message, const std::string& caption, int style)> ThreadSafeMessageBox;
CSignal<void(const std::string& message, const std::string& caption, int style)> ThreadSafeMessageBox;
/** Ask the user whether they want to pay a fee or not. */
boost::signals2::signal<bool (int64_t nFeeRequired, const std::string& strCaption), boost::signals2::last_value<bool> > ThreadSafeAskFee;
CSignal<bool(int64_t nFeeRequired, const std::string& strCaption)> ThreadSafeAskFee;
/** Handle a URL passed at the command line. */
boost::signals2::signal<void (const std::string& strURI)> ThreadSafeHandleURI;
CSignal<void(const std::string& strURI)> ThreadSafeHandleURI;
/** Progress message during initialization. */
boost::signals2::signal<void (const std::string &message)> InitMessage;
CSignal<void(const std::string& message)> InitMessage;
/** Initiate client shutdown. */
boost::signals2::signal<void ()> QueueShutdown;
CSignal<void()> QueueShutdown;
/** Translate a message to the native language of the user. */
boost::signals2::signal<std::string (const char* psz)> Translate;
CSignal<std::string(const char* psz)> Translate;
/** Block chain changed. */
boost::signals2::signal<void ()> NotifyBlocksChanged;
CSignal<void()> NotifyBlocksChanged;
/** Number of network connections changed. */
boost::signals2::signal<void (int newNumConnections)> NotifyNumConnectionsChanged;
/**
* New, updated or cancelled alert.
* @note called with lock cs_mapAlerts held.
*/
boost::signals2::signal<void (const uint256 &hash, ChangeType status)> NotifyAlertChanged;
CSignal<void(int newNumConnections)> NotifyNumConnectionsChanged;
};
extern CClientUIInterface uiInterface;
/**
* Translation function: Call Translate signal on UI interface, which returns a boost::optional result.
* If no translation slot is registered, nothing is returned, and simply return the input.
* Translation function: Call Translate signal on UI interface, which returns
* an std::optional. If no translation slot is registered, fall back to the
* untranslated input.
*/
inline std::string _(const char* psz)
{
boost::optional<std::string> rv = uiInterface.Translate(psz);
return rv ? (*rv) : psz;
std::optional<std::string> rv = uiInterface.Translate(psz);
return rv ? *rv : psz;
}
#endif
+52 -49
View File
@@ -3,32 +3,9 @@
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "util.h"
#include "sync.h"
#include "strlcpy.h"
#include "version.h"
#include "ui_interface.h"
#include <boost/algorithm/string/join.hpp>
// Work around clang compilation problem in Boost 1.46:
// /usr/include/boost/program_options/detail/config_file.hpp:163:17: error: call to function 'to_internal' that is neither visible in the template definition nor found by argument-dependent lookup
// See also: http://stackoverflow.com/questions/10020179/compilation-fail-in-boost-librairies-program-options
// http://clang.debian.net/status.php?version=3.0&key=CANNOT_FIND_FUNCTION
namespace boost {
namespace program_options {
std::string to_internal(const std::string&);
}
}
#include <boost/program_options/detail/config_file.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/thread.hpp>
#include <openssl/crypto.h>
#include <openssl/rand.h>
#include <stdarg.h>
// On Windows, include shell/COM headers FIRST so their `byte` typedef
// is established before any std header pulls in `std::byte` (C++17).
// Otherwise the names collide when COM headers reference `byte`.
#ifdef WIN32
#ifdef _MSC_VER
#pragma warning(disable:4786)
@@ -58,6 +35,32 @@ namespace boost {
#include <execinfo.h>
#endif
#include "util.h"
#include "sync.h"
#include "strlcpy.h"
#include "version.h"
#include "ui_interface.h"
#include <boost/algorithm/string/join.hpp>
// Work around clang compilation problem in Boost 1.46:
// /usr/include/boost/program_options/detail/config_file.hpp:163:17: error: call to function 'to_internal' that is neither visible in the template definition nor found by argument-dependent lookup
// See also: http://stackoverflow.com/questions/10020179/compilation-fail-in-boost-librairies-program-options
// http://clang.debian.net/status.php?version=3.0&key=CANNOT_FIND_FUNCTION
namespace boost {
namespace program_options {
std::string to_internal(const std::string&);
}
}
#include <boost/program_options/detail/config_file.hpp>
#include <boost/program_options/parsers.hpp>
#include <filesystem>
#include <fstream>
#include <thread>
#include <openssl/crypto.h>
#include <openssl/rand.h>
#include <stdarg.h>
using namespace std;
@@ -226,7 +229,7 @@ inline int OutputDebugStringF(const char* pszFormat, ...)
if (!fileout)
{
boost::filesystem::path pathDebug = GetDataDir() / "debug.log";
std::filesystem::path pathDebug = GetDataDir() / "debug.log";
fileout = fopen(pathDebug.string().c_str(), "a");
if (fileout) setbuf(fileout, NULL); // unbuffered
}
@@ -238,14 +241,14 @@ inline int OutputDebugStringF(const char* pszFormat, ...)
// Since the order of destruction of static/global objects is undefined,
// allocate mutexDebugLog on the heap the first time this routine
// is called to avoid crashes during shutdown.
static boost::mutex* mutexDebugLog = NULL;
if (mutexDebugLog == NULL) mutexDebugLog = new boost::mutex();
boost::mutex::scoped_lock scoped_lock(*mutexDebugLog);
static std::mutex* mutexDebugLog = NULL;
if (mutexDebugLog == NULL) mutexDebugLog = new std::mutex();
std::lock_guard<std::mutex> scoped_lock(*mutexDebugLog);
// reopen the log file, if requested
if (fReopenDebugLog) {
fReopenDebugLog = false;
boost::filesystem::path pathDebug = GetDataDir() / "debug.log";
std::filesystem::path pathDebug = GetDataDir() / "debug.log";
if (freopen(pathDebug.string().c_str(),"a",fileout) != NULL)
setbuf(fileout, NULL); // unbuffered
}
@@ -1015,9 +1018,9 @@ void PrintExceptionContinue(std::exception* pex, const char* pszThread)
strMiscWarning = message;
}
boost::filesystem::path GetDefaultDataDir()
std::filesystem::path GetDefaultDataDir()
{
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
// Windows < Vista: C:\Documents and Settings\Username\Application Data\triangles
// Windows >= Vista: C:\Users\Username\AppData\Roaming\triangles
// Mac: ~/Library/Application Support/triangles
@@ -1044,9 +1047,9 @@ boost::filesystem::path GetDefaultDataDir()
#endif
}
const boost::filesystem::path &GetDataDir(bool fNetSpecific)
const std::filesystem::path &GetDataDir(bool fNetSpecific)
{
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
static fs::path pathCached[2];
static CCriticalSection csPathCached;
@@ -1062,7 +1065,7 @@ const boost::filesystem::path &GetDataDir(bool fNetSpecific)
LOCK(csPathCached);
if (mapArgs.count("-datadir")) {
path = fs::system_complete(mapArgs["-datadir"]);
path = fs::absolute(mapArgs["-datadir"]);
if (!fs::is_directory(path)) {
path = "";
return path;
@@ -1079,9 +1082,9 @@ const boost::filesystem::path &GetDataDir(bool fNetSpecific)
return path;
}
boost::filesystem::path GetConfigFile()
std::filesystem::path GetConfigFile()
{
boost::filesystem::path pathConfigFile(GetArg("-conf", "triangles.conf"));
std::filesystem::path pathConfigFile(GetArg("-conf", "triangles.conf"));
if (!pathConfigFile.is_absolute()) pathConfigFile = GetDataDir(false) / pathConfigFile;
return pathConfigFile;
}
@@ -1089,7 +1092,7 @@ boost::filesystem::path GetConfigFile()
void ReadConfigFile(map<string, string>& mapSettingsRet,
map<string, vector<string> >& mapMultiSettingsRet)
{
boost::filesystem::ifstream streamConfig(GetConfigFile());
std::ifstream streamConfig(GetConfigFile());
if (!streamConfig.good())
return; // No triangles.conf file is OK
@@ -1110,15 +1113,15 @@ void ReadConfigFile(map<string, string>& mapSettingsRet,
}
}
boost::filesystem::path GetPidFile()
std::filesystem::path GetPidFile()
{
boost::filesystem::path pathPidFile(GetArg("-pid", "trianglesd.pid"));
std::filesystem::path pathPidFile(GetArg("-pid", "trianglesd.pid"));
if (!pathPidFile.is_absolute()) pathPidFile = GetDataDir() / pathPidFile;
return pathPidFile;
}
#ifndef WIN32
void CreatePidFile(const boost::filesystem::path &path, pid_t pid)
void CreatePidFile(const std::filesystem::path &path, pid_t pid)
{
FILE* file = fopen(path.string().c_str(), "w");
if (file)
@@ -1129,7 +1132,7 @@ void CreatePidFile(const boost::filesystem::path &path, pid_t pid)
}
#endif
bool RenameOver(boost::filesystem::path src, boost::filesystem::path dest)
bool RenameOver(std::filesystem::path src, std::filesystem::path dest)
{
#ifdef WIN32
return MoveFileExA(src.string().c_str(), dest.string().c_str(),
@@ -1153,9 +1156,9 @@ void FileCommit(FILE *fileout)
void ShrinkDebugFile()
{
// Scroll debug.log if it's getting too big
boost::filesystem::path pathLog = GetDataDir() / "debug.log";
std::filesystem::path pathLog = GetDataDir() / "debug.log";
FILE* file = fopen(pathLog.string().c_str(), "r");
if (file && boost::filesystem::file_size(pathLog) > 10 * 1000000)
if (file && std::filesystem::file_size(pathLog) > 10 * 1000000)
{
// Restart the file with some of the end
char pch[200000];
@@ -1291,9 +1294,9 @@ std::string FormatSubVersion(const std::string& name, int nClientVersion, const
}
#ifdef WIN32
boost::filesystem::path GetSpecialFolderPath(int nFolder, bool fCreate)
std::filesystem::path GetSpecialFolderPath(int nFolder, bool fCreate)
{
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
char pszPath[MAX_PATH] = "";
@@ -1339,8 +1342,8 @@ bool NewThread(void(*pfn)(void*), void* parg)
{
try
{
boost::thread(pfn, parg); // thread detaches when out of scope
} catch(boost::thread_resource_error &e) {
std::thread(pfn, parg).detach();
} catch(const std::system_error& e) {
printf("Error creating thread: %s\n", e.what());
return false;
}
+15 -21
View File
@@ -18,11 +18,9 @@
#include <vector>
#include <string>
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/date_time/gregorian/gregorian_types.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <chrono>
#include <thread>
#include <filesystem>
#include <openssl/sha.h>
#include <openssl/ripemd.h>
@@ -107,11 +105,7 @@ T* alignup(T* p)
inline void MilliSleep(int64_t n)
{
#if BOOST_VERSION >= 105000
boost::this_thread::sleep_for(boost::chrono::milliseconds(n));
#else
boost::this_thread::sleep(boost::posix_time::milliseconds(n));
#endif
std::this_thread::sleep_for(std::chrono::milliseconds(n));
}
/* This GNU C extension enables the compiler to check the format string against the parameters provided.
@@ -202,17 +196,17 @@ void ParseParameters(int argc, const char*const argv[]);
bool WildcardMatch(const char* psz, const char* mask);
bool WildcardMatch(const std::string& str, const std::string& mask);
void FileCommit(FILE *fileout);
bool RenameOver(boost::filesystem::path src, boost::filesystem::path dest);
boost::filesystem::path GetDefaultDataDir();
const boost::filesystem::path &GetDataDir(bool fNetSpecific = true);
boost::filesystem::path GetConfigFile();
boost::filesystem::path GetPidFile();
bool RenameOver(std::filesystem::path src, std::filesystem::path dest);
std::filesystem::path GetDefaultDataDir();
const std::filesystem::path &GetDataDir(bool fNetSpecific = true);
std::filesystem::path GetConfigFile();
std::filesystem::path GetPidFile();
#ifndef WIN32
void CreatePidFile(const boost::filesystem::path &path, pid_t pid);
void CreatePidFile(const std::filesystem::path &path, pid_t pid);
#endif
void ReadConfigFile(std::map<std::string, std::string>& mapSettingsRet, std::map<std::string, std::vector<std::string> >& mapMultiSettingsRet);
#ifdef WIN32
boost::filesystem::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
std::filesystem::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
#endif
void ShrinkDebugFile();
int GetRandInt(int nMax);
@@ -343,14 +337,14 @@ inline int64_t GetPerformanceCounter()
inline int64_t GetTimeMillis()
{
return (boost::posix_time::ptime(boost::posix_time::microsec_clock::universal_time()) -
boost::posix_time::ptime(boost::gregorian::date(1970,1,1))).total_milliseconds();
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
}
inline int64_t GetTimeMicros()
{
return (boost::posix_time::ptime(boost::posix_time::microsec_clock::universal_time()) -
boost::posix_time::ptime(boost::gregorian::date(1970,1,1))).total_microseconds();
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
}
inline std::string DateTimeStrFormat(const char* pszFormat, int64_t nTime)
+157
View File
@@ -0,0 +1,157 @@
// 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_SIGNAL_H
#define TRIANGLES_SIGNAL_H
#include <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
/**
* Lightweight multi-listener signal built on std::function.
*
* Two variants via partial specialization on a function signature:
* CSignal<void(Args...)> fan-out signal, operator() returns void.
* CSignal<R(Args...)> returns std::optional<R> from the most-recently
* connected slot (mirroring signals2's last_value
* policy with optional fallback when no slots are connected).
*
* connect(slot) returns a Connection token; call .disconnect() to unsubscribe.
*
* Disconnect-by-bind-identity (signals2's compare-by-equivalent-bind trick) is
* intentionally not provided std::function lacks equality. Callers should
* keep the Connection token (or a CSignalConnections bag) and disconnect via it.
*/
template <typename Signature> class CSignal;
namespace signal_detail {
template <typename Slot>
struct SignalState
{
// Renamed from `slots` because Qt's MOC headers `#define slots` to expand
// empty, which strips the member name in any TU that pulls in <QtCore>.
std::map<std::size_t, Slot> slot_map;
std::size_t next_id = 0;
};
template <typename Slot>
class ConnectionT
{
public:
ConnectionT() = default;
void disconnect()
{
if (auto sp = m_state.lock()) {
sp->slot_map.erase(m_id);
}
m_state.reset();
}
template <typename> friend class SignalBase;
ConnectionT(std::weak_ptr<SignalState<Slot>> state, std::size_t id)
: m_state(std::move(state)), m_id(id) {}
private:
std::weak_ptr<SignalState<Slot>> m_state;
std::size_t m_id = 0;
};
template <typename Slot>
class SignalBase
{
public:
using slot_type = Slot;
using Connection = ConnectionT<Slot>;
SignalBase() : m_state(std::make_shared<SignalState<Slot>>()) {}
SignalBase(const SignalBase&) = delete;
SignalBase& operator=(const SignalBase&) = delete;
Connection connect(slot_type slot)
{
std::size_t id = ++m_state->next_id;
m_state->slot_map.emplace(id, std::move(slot));
return Connection(m_state, id);
}
bool empty() const { return m_state->slot_map.empty(); }
protected:
std::shared_ptr<SignalState<Slot>> m_state;
};
} // namespace signal_detail
// Void-returning specialization: invoke every connected slot.
template <typename... Args>
class CSignal<void(Args...)> : public signal_detail::SignalBase<std::function<void(Args...)>>
{
public:
template <typename... CallArgs>
void operator()(CallArgs&&... args) const
{
// Snapshot lets slots mutate connections during invocation.
auto snapshot = this->m_state->slot_map;
for (auto& kv : snapshot) {
if (kv.second) kv.second(args...);
}
}
};
// Non-void specialization: invoke every slot, return the most-recent slot's
// result wrapped in optional (empty if no slots are connected).
template <typename R, typename... Args>
class CSignal<R(Args...)> : public signal_detail::SignalBase<std::function<R(Args...)>>
{
public:
template <typename... CallArgs>
std::optional<R> operator()(CallArgs&&... args) const
{
auto snapshot = this->m_state->slot_map;
std::optional<R> result;
for (auto& kv : snapshot) {
if (kv.second) result = kv.second(args...);
}
return result;
}
};
/**
* Holder for a heterogeneous list of signal connections that should all be
* released together (typical pattern: subscribe in ctor, drop on dtor).
*/
class CSignalConnections
{
public:
CSignalConnections() = default;
CSignalConnections(const CSignalConnections&) = delete;
CSignalConnections& operator=(const CSignalConnections&) = delete;
template <typename Conn>
void add(Conn conn)
{
auto holder = std::make_shared<Conn>(std::move(conn));
m_disconnectors.emplace_back([holder]() { holder->disconnect(); });
}
void disconnect_all()
{
for (auto& d : m_disconnectors) d();
m_disconnectors.clear();
}
~CSignalConnections() { disconnect_all(); }
private:
std::vector<std::function<void()>> m_disconnectors;
};
#endif // TRIANGLES_SIGNAL_H
+66 -101
View File
@@ -9,13 +9,7 @@
#include "util.h"
#include "ui_interface.h"
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <leveldb/db.h>
#include <leveldb/write_batch.h>
#include <leveldb/cache.h>
#include <leveldb/filter_policy.h>
#include <filesystem>
#include <openssl/sha.h>
@@ -23,10 +17,7 @@
#include <algorithm>
#include <cstdio>
namespace fs = boost::filesystem;
// Global LevelDB pointer (defined in txdb-leveldb.cpp)
extern leveldb::DB *txdb;
namespace fs = std::filesystem;
namespace UtxoSnapshot {
@@ -61,12 +52,14 @@ bool DumpSnapshot(const fs::path& destPath,
std::reverse(vHeaders.begin(), vHeaders.end());
}
// Open the chain DB once and reuse for both the UTXO count and the
// iteration below. Backend-agnostic via the CTxDBBase abstraction.
auto txdbHolder = MakeChainDB("r");
CTxDBBase& txdbRead = *txdbHolder;
// Count UTXOs first
int nUtxoCount = 0;
{
CTxDB txdbRead("r");
txdbRead.SumUtxoValues(nUtxoCount);
}
txdbRead.SumUtxoValues(nUtxoCount);
if (nUtxoCount == 0) {
strError = "No UTXOs found in database";
@@ -125,31 +118,31 @@ bool DumpSnapshot(const fs::path& destPath,
SHA256_Update(&sha256, strEntry.data(), entrySize);
}
// Write UTXO section using LevelDB iterator (same pattern as SumUtxoValues)
// Write UTXO section using the backend-agnostic iterator (same pattern as
// SumUtxoValues). Iteration runs outside any active batch — the contract
// documented on CTxDBIteratorBase guarantees a stable view of committed state.
{
CDataStream ssKeyPrefix(SER_DISK, CLIENT_VERSION);
ssKeyPrefix << std::make_pair(std::string("u"), std::make_pair(uint256(0), (unsigned int)0));
std::string strPrefixBegin = ssKeyPrefix.str();
leveldb::Iterator* it = txdb->NewIterator(leveldb::ReadOptions());
auto it = txdbRead.NewIterator();
unsigned int nWritten = 0;
for (it->Seek(strPrefixBegin); it->Valid(); it->Next()) {
// Check key prefix is still "u"
CDataStream ssKey(it->key().data(), it->key().data() + it->key().size(), SER_DISK, CLIENT_VERSION);
std::string strKey = it->KeyStr();
CDataStream ssKey(strKey.data(), strKey.data() + strKey.size(), SER_DISK, CLIENT_VERSION);
std::string strKeyType;
ssKey >> strKeyType;
if (strKeyType != "u")
break;
// Extract outpoint from key
uint256 txhash;
unsigned int nIndex;
ssKey >> txhash;
ssKey >> nIndex;
// Extract UTXO entry from value
CDataStream ssValue(it->value().data(), it->value().data() + it->value().size(), SER_DISK, CLIENT_VERSION);
std::string strRawValue = it->ValueStr();
CDataStream ssValue(strRawValue.data(), strRawValue.data() + strRawValue.size(), SER_DISK, CLIENT_VERSION);
CUtxoEntry entry;
ssValue >> entry;
@@ -171,7 +164,6 @@ bool DumpSnapshot(const fs::path& destPath,
if (nWritten % 10000 == 0)
printf("UtxoSnapshot: wrote %d / %d UTXOs\n", nWritten, nUtxoCount);
}
delete it;
// Update actual count (in case it changed during iteration)
if (nWritten != numUtxos) {
@@ -203,7 +195,7 @@ bool DumpSnapshot(const fs::path& destPath,
// ---------------------------------------------------------------------------
bool LoadSnapshot(const fs::path& snapshotPath,
const fs::path& dataDir,
const fs::path& /*dataDir — unused; resolved per-backend via GetChainDataDir()*/,
std::string& strError)
{
FILE* file = fopen(snapshotPath.string().c_str(), "rb");
@@ -271,47 +263,42 @@ bool LoadSnapshot(const fs::path& snapshotPath,
printf("UtxoSnapshot: loading snapshot at height %d (%d headers, %d UTXOs)\n",
height, numHeaders, numUtxos);
// Create fresh LevelDB directory
fs::path txleveldbPath = dataDir / "txleveldb";
if (fs::exists(txleveldbPath))
fs::remove_all(txleveldbPath);
fs::create_directories(txleveldbPath);
// Wipe the chain DB directory for the configured backend, then open fresh
// via the factory. Must run before any other code touches the chain DB
// (the global handle is opened lazily on first MakeChainDB call).
WipeChainDataDir();
// Open LevelDB directly (not via CTxDB - it's not initialized yet)
leveldb::Options options;
int nCacheSizeMB = GetArg("-dbcache", 2048);
options.block_cache = leveldb::NewLRUCache(nCacheSizeMB * 1048576);
options.filter_policy = leveldb::NewBloomFilterPolicy(10);
options.write_buffer_size = 64 * 1048576;
options.max_open_files = 1000;
options.create_if_missing = true;
leveldb::DB* pdb = NULL;
leveldb::Status status = leveldb::DB::Open(options, txleveldbPath.string(), &pdb);
if (!status.ok()) {
auto txdbHolder = MakeChainDB("c+");
if (!txdbHolder) {
fclose(file);
delete options.filter_policy;
delete options.block_cache;
strError = "Cannot create LevelDB: " + status.ToString();
strError = "Failed to open fresh chain DB";
return false;
}
CTxDBBase& txdb = *txdbHolder;
SHA256_CTX sha256;
SHA256_Init(&sha256);
leveldb::WriteBatch batch;
bool success = true;
unsigned int nBatchSize = 0;
if (!txdb.TxnBegin()) {
fclose(file);
strError = "Failed to begin chain DB transaction";
return false;
}
auto flushBatch = [&]() -> bool {
if (nBatchSize == 0)
return true;
leveldb::Status s = pdb->Write(leveldb::WriteOptions(), &batch);
if (!s.ok()) {
strError = "LevelDB write failed: " + s.ToString();
if (!txdb.TxnCommit()) {
strError = "Chain DB batch commit failed";
return false;
}
if (!txdb.TxnBegin()) {
strError = "Chain DB batch restart failed";
return false;
}
batch.Clear();
nBatchSize = 0;
return true;
};
@@ -345,14 +332,11 @@ bool LoadSnapshot(const fs::path& snapshotPath,
ssEntry >> entryHash;
ssEntry >> diskindex;
// Write to LevelDB as "blockindex" key
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey << std::make_pair(std::string("blockindex"), entryHash);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
ssValue << diskindex;
batch.Put(ssKey.str(), ssValue.str());
if (!txdb.WriteBlockIndex(diskindex)) {
success = false;
strError = "WriteBlockIndex failed at header " + std::to_string(i);
break;
}
nBatchSize++;
if (nBatchSize >= 1000) {
@@ -394,14 +378,11 @@ bool LoadSnapshot(const fs::path& snapshotPath,
ssRecord >> nIndex;
ssRecord >> entry;
// Write to LevelDB with "u" prefix key
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey << std::make_pair(std::string("u"), std::make_pair(txhash, nIndex));
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
ssValue << entry;
batch.Put(ssKey.str(), ssValue.str());
if (!txdb.WriteUtxo(txhash, nIndex, entry)) {
success = false;
strError = "WriteUtxo failed at index " + std::to_string(i);
break;
}
nBatchSize++;
if (nBatchSize >= 50000) {
@@ -431,50 +412,34 @@ bool LoadSnapshot(const fs::path& snapshotPath,
}
}
// Write metadata
// Write metadata via the abstraction's named operations. These produce
// bit-identical key bytes across backends, so the new DB is in the same
// canonical state as it would be after a normal IBD.
if (success) {
leveldb::WriteBatch metaBatch;
// hashBestChain
CDataStream ssKey1(SER_DISK, CLIENT_VERSION);
ssKey1 << std::string("hashBestChain");
CDataStream ssVal1(SER_DISK, CLIENT_VERSION);
ssVal1 << blockHash;
metaBatch.Put(ssKey1.str(), ssVal1.str());
// dbformat = 3
CDataStream ssKey2(SER_DISK, CLIENT_VERSION);
ssKey2 << std::string("dbformat");
CDataStream ssVal2(SER_DISK, CLIENT_VERSION);
ssVal2 << (int)3;
metaBatch.Put(ssKey2.str(), ssVal2.str());
// version
CDataStream ssKey3(SER_DISK, CLIENT_VERSION);
ssKey3 << std::string("version");
CDataStream ssVal3(SER_DISK, CLIENT_VERSION);
ssVal3 << DATABASE_VERSION;
metaBatch.Put(ssKey3.str(), ssVal3.str());
leveldb::Status s = pdb->Write(leveldb::WriteOptions(), &metaBatch);
if (!s.ok()) {
if (!txdb.TxnBegin()) {
success = false;
strError = "Failed to write metadata: " + s.ToString();
strError = "Failed to begin metadata transaction";
}
}
if (success) {
if (!txdb.WriteHashBestChain(blockHash) ||
!txdb.WriteDbFormat(3) ||
!txdb.WriteVersion(DATABASE_VERSION) ||
!txdb.TxnCommit())
{
success = false;
strError = "Failed to write snapshot metadata";
}
}
// Clean up LevelDB
delete pdb;
delete options.filter_policy;
delete options.block_cache;
fclose(file);
if (!success) {
// Remove corrupted/incomplete database
// Roll back any pending batch and remove the partial DB so the next
// startup begins from a clean slate.
txdb.TxnAbort();
printf("UtxoSnapshot: load failed: %s\n", strError.c_str());
if (fs::exists(txleveldbPath))
fs::remove_all(txleveldbPath);
WipeChainDataDir();
return false;
}
+4 -4
View File
@@ -5,7 +5,7 @@
#define TRIANGLES_UTXOSNAPSHOT_H
#include <string>
#include <boost/filesystem.hpp>
#include <filesystem>
// UTXO snapshot file magic bytes
static const unsigned int UTXO_SNAPSHOT_MAGIC = 0x53585455; // "UTXS" little-endian
@@ -22,7 +22,7 @@ namespace UtxoSnapshot {
// Create a UTXO snapshot from the current chain state.
// Writes last nHeaders block index entries + all UTXOs to destPath.
// Returns true on success, sets strError on failure.
bool DumpSnapshot(const boost::filesystem::path& destPath,
bool DumpSnapshot(const std::filesystem::path& destPath,
unsigned int nHeaders,
std::string& strError);
@@ -30,8 +30,8 @@ namespace UtxoSnapshot {
// Writes block index entries, UTXOs, hashBestChain, and dbformat.
// The LevelDB must NOT be open yet (call before LoadBlockIndex).
// Returns true on success, sets strError on failure.
bool LoadSnapshot(const boost::filesystem::path& snapshotPath,
const boost::filesystem::path& dataDir,
bool LoadSnapshot(const std::filesystem::path& snapshotPath,
const std::filesystem::path& dataDir,
std::string& strError);
} // namespace UtxoSnapshot
+11 -11
View File
@@ -663,7 +663,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn)
if ( !strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
boost::thread t(runCommand, strCmd); // thread runs free
std::thread(runCommand, strCmd).detach(); // thread runs free
}
}
@@ -1041,7 +1041,7 @@ bool CWallet::ScanForWalletTransactionsFromIndex(CBlockIndex* pindexStart, bool
setScripts.insert((*it).first);
}
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
set<uint256> setSeedTxIds;
for (const CKeyID& keyId : setKeys)
@@ -1162,7 +1162,7 @@ int CWallet::ScanForWalletTransaction(const uint256& hashTx)
void CWallet::ReacceptWalletTransactions()
{
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
bool fRepeat = true;
while (fRepeat)
{
@@ -1251,7 +1251,7 @@ void CWalletTx::RelayWalletTransaction(CTxDBBase& txdb)
void CWalletTx::RelayWalletTransaction()
{
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
RelayWalletTransaction(txdb);
}
@@ -1278,7 +1278,7 @@ void CWallet::ResendWalletTransactions(bool fForce)
// Rebroadcast any of our txes that aren't in a block yet
printf("ResendWalletTransactions()\n");
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
{
LOCK(cs_wallet);
// Sort them in chronological order
@@ -1701,7 +1701,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend,
{
LOCK2(cs_main, cs_wallet);
// txdb must be opened before the mapWallet lock
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
{
nFeeRet = nTransactionFee;
while (true)
@@ -1895,7 +1895,7 @@ bool CWallet::GetStakeWeight(const CKeyStore& keystore, uint64_t& nMinWeight, ui
}
}
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
int64_t nNow = GetTime();
for (auto& sc : vStakeCoins)
{
@@ -1962,7 +1962,7 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int
int64_t nCredit = 0;
CScript scriptPubKeyKernel;
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
for (auto pcoin : setCoins)
{
CTxIndex txindex;
@@ -2104,7 +2104,7 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int
// Calculate coin age reward
{
uint64_t nCoinAge;
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
if (!txNew.GetCoinAge(txdb, nCoinAge))
return error("CreateCoinStake : failed to calculate coin age");
@@ -2231,7 +2231,7 @@ string CWallet::SendMoney(CScript scriptPubKey, int64_t nValue, std::string& sNa
return strError;
}
if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending...")))
if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending...")).value_or(false))
return "ABORTED";
if (!CommitTransaction(wtxNew, reservekey))
@@ -2689,7 +2689,7 @@ void CWallet::FixSpentCoins(int& nMismatchFound, int64_t& nBalanceInQuestion, bo
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
vCoins.push_back(&(*it).second);
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
for (CWalletTx* pcoin : vCoins)
{
uint256 hashTx = pcoin->GetHash();
+2 -2
View File
@@ -334,12 +334,12 @@ public:
/** Address book entry changed.
* @note called with lock cs_wallet held.
*/
boost::signals2::signal<void (CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, ChangeType status)> NotifyAddressBookChanged;
CSignal<void(CWallet*, const CTxDestination&, const std::string&, bool, ChangeType)> NotifyAddressBookChanged;
/** Wallet transaction added, removed or updated.
* @note called with lock cs_wallet held.
*/
boost::signals2::signal<void (CWallet *wallet, const uint256 &hashTx, ChangeType status)> NotifyTransactionChanged;
CSignal<void(CWallet*, const uint256&, ChangeType)> NotifyTransactionChanged;
};
/** A key allocated from the key pool. */
+2 -3
View File
@@ -5,12 +5,11 @@
#include "walletdb.h"
#include "wallet.h"
#include <filesystem>
#include <boost/version.hpp>
#include <boost/filesystem.hpp>
using namespace std;
using namespace boost;
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
static uint64_t nAccountingEntryNumber = 0;