Compare commits

..

138 Commits

Author SHA1 Message Date
triangles-bot c606253c41 utxosnapshot: build address index when loading a UTXO snapshot (fast-start nodes get balances) [v5.9.17] 2026-06-16 20:37:44 -07:00
triangles-bot b2dfb627cc main: build address index during FastImport (fix-in-place, v5.9.16) 2026-06-16 20:24:26 -07:00
triangles-bot d0a76f8ae2 qt: show Seed Phrase (HD Backup) in the visible Operations menu (v5.9.15)
The HD seed action was only added to the standard Qt menu bar, which the
skinned GUI hides. Add it to menuOperationsRequested() so users can actually
reach Generate / Reveal-for-backup / Restore from the Operations menu.
2026-06-16 16:24:12 -07:00
SamiAhmed7777 cc57c906b4 Merge PR #6: HD seed-phrase wallet + fast-sync checkpoint/snapshot (v5.9.14)
HD wallet (BIP39/BIP32 seed phrases) - daemon + Qt
2026-06-15 20:44:52 -07:00
Sami e80d672833 checkpoints: add 2206004 checkpoint + UTXO snapshot hash (fast new-node sync) 2026-06-15 20:31:02 -07:00
Sami fcdc9a58b0 ci(lint): checkout secp256k1 submodule for clang-tidy (fixes configure) 2026-06-15 19:26:43 -07:00
Sami 514867c5d9 wallet(HD): flush keypool on seed set so getnewaddress yields HD keys immediately 2026-06-15 19:16:03 -07:00
Sami c464e6c59d wallet(HD): Qt UI - Seed Phrase dialog (generate/restore/backup)
Adds HDSeedDialog (Settings > Seed Phrase) with Generate New / Reveal for Backup / Restore from Phrase, driven by new WalletModel HD methods. Restore rescans the chain. Requires wallet unlock via the standard UnlockContext.
2026-06-15 19:16:03 -07:00
Sami 11ed086d1e wallet(HD): native BIP39/BIP32 HD wallet - daemon side
Adds deterministic HD key derivation (path m/44'/2222'/0'/0/i, matching the TRIdock web wallet) wired into CWallet: HD seed stored in wallet.dat (encrypted with the wallet master key when the wallet is encrypted), keypool derived from the seed, and new RPC commands hdnew/hdrestore/hdshow/hdinfo. Crypto core verified standalone against the official BIP39 vector and triWallet.js addresses.
2026-06-15 19:15:43 -07:00
Hermes 5511cfae6b v5.9.14 + pitfall #61 guard: initialize pindexFinalized on startup
ROOT CAUSE of the 2026-06-16 minority-fork reorg:

The v5.9.14 getheaders handler serves headers from pindexGenesisBlock
when a fork peer sends a locator that doesn't match our main chain. The
intent was to help fork peers learn the canonical chain. The bug: this
allows the fork peer to feed us THEIR short chain back via getheaders,
and we accept it because:

1. pindexFinalized is NULL on a fresh restart (the auto-checkpoint code
   in ActivateBestChain() at main.cpp:2459 only sets it when
   !IsInitialBlockDownload(), but a synced daemon restarting with a
   chain tip > 24h stale is considered IBD by the time check at
   main.cpp:1331).

2. With pindexFinalized = NULL, the reorg guard at main.cpp:2198
   short-circuits: 'if (pindexFinalized && pfork->nHeight < ...)'.

3. The fork peer's 3,755-block chain gets accepted, overwriting our
   healthy 2,206,004-block chain.

THE FIX (two parts):

A. init.cpp:1083-1113 — after LoadBlockIndex(), call
   Checkpoints::GetLastCheckpoint(mapBlockIndex) to initialize
   pindexFinalized from the hardcoded checkpoint (block 2,205,000).
   This is a no-op on the first ~10 seconds of the daemon's life
   (during the initial IBD walk), but as soon as we sync past block
   2,205,000, the checkpoint is in mapBlockIndex and pindexFinalized
   is set for the daemon's entire lifetime.

B. main.cpp:4473-4496 — in the getheaders fork-detection branch,
   serve from pindexFinalized->pnext (the block after our last
   finalized checkpoint) instead of pindexGenesisBlock. This is the
   safe equivalent of the v5.9.14 'serve from genesis' logic — the
   peer learns our canonical chain from the most recently finalized
   point forward, and their short fork gets rejected at the reorg
   guard in Reorganize() because the fork point is below
   pindexFinalized.

Combined: a fork peer can no longer drag us below block 2,205,000
because (a) pindexFinalized is always set on a synced restart, and
(b) the reorg guard now sees a non-NULL pindexFinalized and rejects
any fork below it.

Tested manually by simulating a restart with the v5.9.14 binary on
a chain that had been reorged to a minority fork; the new build
refuses the reorg and prints 'STARTUP-CHECKPOINT: pindexFinalized set
to block 2205000' on startup, then 'getheaders: fork detected from
peer ... serving headers from finalized block 2205000' on the first
fork peer's getheaders request.
2026-06-15 18:37:53 -07:00
Sami Ahmed 1dda8b3006 Auto-repair corrupt Tor state file on daemon startup (pitfall #19)
Tor's atomic state-write is: write state.tmp, then rename to state.
If the daemon is killed mid-write (pkill -9, OOM, power loss, disk-full),
the rename can fail and 'state' is left as a regular file instead of a
directory. On next start, Tor's config validator refuses to use it:

  [warn] State file '...' is not a file? Failing.
  [err] set_options: Bug: Acting on config options left us in a broken state. Dying.
  [err] Reading config failed--see warnings above.

The daemon then reports 'Tor failed to start. Triangles requires Tor to
operate.' (the error from the 2026-06-15 TRI-LAPTOP GUI wallet
screenshot) and refuses to come up at all.

Hit before on DNS3 (2026-05-24, fixed by manual 'mv state state.file.bak;
mkdir state' on the operator) and on the laptop today. The fix was always
the same one-liner; this patch makes the daemon do it itself.

Behavior:
- Detect: if tor_data/state exists and is NOT a directory, it's corrupt
- Quarantine: rename to tor_data/state.corrupt-YYYYMMDD-HHMMSS for
  inspection (the user might want to recover the file's contents)
- If rename fails (Windows anti-virus holds the file, etc.), retry with
  remove-then-rename, and as a last resort just remove() so Tor can
  proceed
- Print a clear 'Tor state was a file (corrupt) — quarantined to ...'
  log line so the operator can see it happened
- Tor then recreates state/ as a fresh directory and bootstraps normally

This is a pure-additive change (no existing behavior modified). Builds
clean with the existing C++20 + libtor.a toolchain. No version bump
needed - will roll into the next CI cycle.
2026-06-15 16:29:05 -07:00
Sami Ahmed 68c4e38411 Fix getheaders pnext null bug: serve main chain from genesis, tip-backwards fallback
Two related bugs in the getheaders handler at main.cpp:4441:

1. Locator-mismatch returns 0 headers:
   GetBlockIndex() falls through to pindexGenesisBlock when no locator
   hash matches the main chain. pindexGenesisBlock->pnext is null, so
   the for-loop exits immediately and the peer gets an empty headers
   response. This was the DNS3 headers-first sync stall (pitfall #36):
   'getheaders -1 to 0000...' logged repeatedly.

   Mirror the getblocks handler (line 4387): detect the mismatch, log
   a warning, and serve headers from genesis so the peer can discover
   the canonical chain.

2. Broken pnext chain at any height:
   Even when GetBlockIndex() returns a valid (non-genesis) block, its
   pnext can be null — this happens when LoadBlockIndex() didn't fully
   heal pnext links (e.g., the node was bootstrapped from a snapshot,
   or the chain was interrupted by a crash). On tridock every pnext
   link was null despite 2.2M blocks (the June 11 'Heal pnext links'
   commit addressed a similar symptom for GetKernelStakeModifier() but
   doesn't reach into the getheaders handler).

   Fall back to a tip-backwards walk from pindexBest when pnext is
   null: O(N) but correct, and only triggers on the broken path.

   (References the design in references/getheaders-pnext-fix.md from
   the v5.9.10 deploy notes — that fix was never actually committed,
   only prototyped and dropped during the June 4 git cleanup.)

Bump to v5.9.14 (also embeds the embedded-Tor 0700 fix from ed996c8).

Refs: SKILL.md pitfall #36
2026-06-15 15:43:03 -07:00
Sami Ahmed ed996c8e9d Fix embedded Tor bootstrap (code -1): force 0700 on hidden service dir
Tor's config validator refuses to start a hidden service on any directory
whose permissions are not 0700. The daemon's fs::create_directories() honors
the process umask (0022 on Linux), leaving hidden_service/ at 0755. tor_run_main()
returned -1 with:

  [warn] Permissions on directory .../hidden_service are too permissive.
  [warn] Failed to parse/validate config: Failed to configure rendezvous options.

This was misdiagnosed as a libtor.a build problem (the June 4 rebuild was a
red herring). The real fix is two fs::permissions() calls after create_directories().

Reproduced with a standalone harness linking libtor.a, fixed, SOCKS port 19099
came up in 1 second and Tor began bootstrapping normally.

Also force 0700 on the DataDirectory itself - same validator, same rule.

Refs: SKILL.md pitfall #59
2026-06-15 15:32:28 -07:00
Sami Ahmed cd9e023865 Bump version to v5.9.13 (transparent Qt wallet icon) 2026-06-13 20:49:29 -07:00
Sami Ahmed f273d651ed Make Qt wallet icon transparent (remove white field around triangle) 2026-06-13 20:45:23 -07:00
Sami Ahmed 2ac88b4e0a Add finality checkpoint at 2205000 (anti-fork); v5.9.12 2026-06-13 15:09:44 -07:00
sami7777 c1340441cc Bump version to v5.9.11 2026-06-11 02:57:42 -07:00
sami7777 c99d859781 Fix use-after-move crash in orphan block handling; add -ignoredupstake recovery flag
mapOrphanBlocksByPrev.insert dereferenced pblock2 after std::move'ing it into mapOrphanBlocks - guaranteed null deref (segfault at offset 4) on every orphan block received. Capture hashPrevBlock and the raw pointer before the move.

Also add -ignoredupstake (default off): bypasses the duplicate proof-of-stake rejection so canonical blocks can be imported when a fork twin staking the same outpoint was seen first. Recovery/diagnostic use only.
2026-06-11 01:34:34 -07:00
sami7777 713f69e137 Gate 7-day coin-age soft cap behind activation timestamp
The soft cap (1c068f4, 2026-04-20) shipped without a height/time gate, retroactively invalidating blocks staked earlier with long-aged coins (e.g. coins idle through the 2022-2026 freeze; block f9f976d0 at 2203410 stakes a 3.4-year-old coin). Apply the cap only to stakes at/after 1776000000 (2026-04-12 ~13:20 UTC); historical stakes validate under the rules they were created with.
2026-06-11 01:34:21 -07:00
sami7777 e3f397c7e5 Heal pnext links on active chain at LoadBlockIndex
Persisted hashNext can be stale or zeroed by crash-interrupted reorgs, breaking GetKernelStakeModifier()'s forward walk and silently rejecting valid new PoS blocks ('check kernel failed', hashProof=0). On DNS3 every one of 2,201,458 links was zeroed on disk. Root cause of the 2026-04-24 chain halt. Rebuild the in-memory links from pindexBest at every startup.
2026-06-11 01:34:20 -07:00
Krystie f80fb98f68 Merge master into cpp20-modernization (RPC fix, auto-bootstrap, tor_data fix)
Brings in:
- 79b0c4a: Fix RPC thread crash on bad auth (T001) — adapted to C++20 style
- d0fb2dc: Enable auto-bootstrap for GUI wallets
- 89a480a: Fix tor_data/state directory trap + make -notor work
- 372b252: Fix Windows CI shell for git submodule
- 6c56e41: Init secp256k1 submodule before build

Kept v5.9.9 version (cpp20-modernization's, newer than master's v5.9.7).
CI workflow kept cpp20-modernization's version (submodule init already present).
2026-06-04 18:25:12 -07:00
Krystie 610b61b1b1 Fix chain reorg crash: coinbase timestamp check + corrupted checkpoint hash
CheckBlock() at line 2825 used the heightless FutureDrift() overload,
which hardcodes 90-second drift (post-FORK_HEIGHT_V5_4). During reorgs
from the block-570 fork chain, block 571 (July 2014) has a coinbase
timestamp that legitimately exceeds 90s before block time, causing:
  ERROR: CheckBlock() : coinbase timestamp is too early
  ERROR: Reorganize() : ConnectBlock failed
  -> infinite crash loop (791 iterations recorded)

Root cause: commit a671708 (v5.8.3) tightened drift from 3min to 90sec.
The heightless overload applies this to ALL blocks regardless of height.

Fix: use explicit 10-minute tolerance in CheckBlock (context-free, no
nHeight available). The tight 90-second check is still enforced in
AcceptBlock/ConnectBlock with proper height context.

Also fix corrupted checkpoint hash at block 3935: truncated '07' in
both mainnet and testnet tables during C++20 modernization.
2026-06-04 18:21:43 -07:00
Krystie e8edcd4aa6 Merge remote-tracking branch 'gitea/master' 2026-05-30 23:01:53 -07:00
Krystie 3928f86657 Merge remote-tracking branch 'gitea/master' into cpp20-modernization 2026-05-30 23:00:56 -07:00
Krystie 67d838433d Fix getblocks: serve main chain to fork peers instead of banning them
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
Fork nodes (DNS3/DNS2 stuck on block 570 chain) send locators that
don't match any main chain block. Instead of disconnecting/ banning
after 3 failed getblocks attempts, this change:

- Serves main chain blocks from genesis when locator has no match
- Resets nIncompatibleGetblocks counter after 10 (so we never ban)
- Fork nodes will receive, validate, and automatically reorg to the
  longer/higher-work main chain once they see it

This fixes the 'no common blocks' deadlock while preserving chain
integrity — only a genuinely longer chain can trigger the reorg.
2026-05-24 22:36:13 -07:00
Krystie e3aeff002c Remove block 570 checkpoint - let nodes sync naturally to main chain
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
2026-05-24 21:54:42 -07:00
Krystie 09ccd4339c IBD stall fix: restore IsInitialBlockDownload() time check, add block 570 checkpoint, fix version handler IBD bypass 2026-05-24 15:40:29 -07:00
sami7777 03aa38f1b4 Promote NewIterator() override to public in both chain DB backends
The base class CTxDBBase declares NewIterator() public, but both
backends overrode it in their protected: section. That narrowed the
static access through the derived type, so the migration utility
(which holds concrete CTxDB / CRocksTxDB instances) couldn't call it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 01:01:44 -07:00
sami7777 9389a883f1 Wire CSyncManager + LevelDB->RocksDB chain DB migration
CSyncManager extracts the headers-first IBD planner from main.cpp into
its own translation unit. main.cpp loses ~570 lines of file-scope state
and helper functions; the headers handler, block-delivery latency
tracking, stall-recovery, and per-peer Tick cadence now route through
g_syncManager.

MaybeMigrateLevelDbToRocksDb() is now reachable via -migratechaindb /
-migratechaindbforce in init.cpp. Reads from <datadir>/txleveldb and
writes byte-for-byte identical records into <datadir>/rocksdb via a
new CRocksTxDB::WriteRawRecordForMigration() shim over WriteRaw.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:44:08 -07:00
sami7777 31fa26f03a Drop checkpoints > 2,000,000
Trim mainnet and testnet checkpoint tables to the 2,000,000 entry.
Clears the snapshot-hash entry at 2,203,594 since its corresponding
checkpoint is now gone (per the invariant noted in the comment block).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 03:07:58 -07:00
sami7777 ce96d278cd Bump client version to v6.0.0
Internal refactor milestone for the C++20 modernization series.
No protocol or on-disk format change (version.h untouched).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 03:07:57 -07:00
Krystie 7166b76bad ci: fix test-linux-sanitizers submodule checkout 2026-05-10 19:36:16 -07:00
Krystie 540db0e210 Fix wallet logging and Qt min-fee enum regressions 2026-05-10 19:18:47 -07:00
Krystie 59b75476ca ci: autonomous fix iteration 2
Generated by triangles-ci-loop.sh on 2026-05-10 18:20:35 PDT. See /var/log/triangles-ci-loop.log.
2026-05-10 18:20:35 -07:00
Krystie 0029b34698 ci: autonomous fix iteration 1
Generated by triangles-ci-loop.sh on 2026-05-10 18:02:40 PDT. See /var/log/triangles-ci-loop.log.
2026-05-10 18:02:40 -07:00
Sami 8e03e89764 ci: trigger Build All Platforms on push to cpp20-modernization 2026-05-10 17:47:35 -07:00
Krystie 3b1850af9d ci: autonomous fix iteration 1
Generated by triangles-ci-loop.sh on 2026-05-10 12:24:48 PDT. See /var/log/triangles-ci-loop.log.
2026-05-10 12:24:48 -07:00
Krystie 223029785c Fix Qt wallet model ownership calls 2026-05-10 02:52:39 -07:00
Krystie ce8be45ea5 Fix reserve key wallet pointer ownership 2026-05-10 02:40:47 -07:00
Krystie e6d8c6dbfe Fix REST path parsing redeclarations 2026-05-10 02:33:40 -07:00
Krystie 74ec53040c Fix duplicate auto declaration regressions 2026-05-10 02:22:37 -07:00
Krystie e251a85d7a Fix string GetArg overload ambiguities 2026-05-10 02:14:17 -07:00
Krystie a16533f11b Fix accounting test wallet pointer usage 2026-05-10 02:03:52 -07:00
Krystie b979f7ae7d Fix GetArg overload ambiguity for pid file 2026-05-10 01:53:32 -07:00
Krystie b1e9878849 Fix script OpenSSL and restrict warning regressions 2026-05-10 01:46:39 -07:00
Krystie b47fa91d6c Fix script signature const-correctness regression 2026-05-10 01:38:42 -07:00
Krystie e9e4a0ca82 Fix remaining walletdb and keystore C++20 issues 2026-05-10 01:21:06 -07:00
Krystie f5e2ce5ca9 Fix OpenSSL 3 and fold-expression warning regressions 2026-05-09 21:27:36 -07:00
Krystie cdbbbb5316 Fix wallet and bigint C++20 build regressions 2026-05-09 20:35:59 -07:00
Krystie c5967e9995 Suppress OpenSSL 3 SHA256 deprecation warnings 2026-05-09 20:17:27 -07:00
Krystie 5f61ed8fcb Fix C++20 type-tag and string timestamp regressions 2026-05-09 19:32:44 -07:00
Krystie c3e4a456d8 ci: fetch submodules in GitHub Actions 2026-05-09 03:30:53 -07:00
sami7777 080942b49d C++20 Round 7: [[nodiscard]] on critical bool functions
- Add [[nodiscard]] to validation functions whose return value must be checked:
  CTransaction::IsStandard, CheckTransaction, ConnectInputs, AcceptToMemoryPool
  CBlock::ConnectBlock, AcceptBlock, IsInitialBlockDownload
  IsStandard, IsMine (3 overloads), SignSignature (2), VerifyScript, VerifySignature
  CWallet::IsMine (3 overloads)
2026-05-08 23:54:05 -07:00
sami7777 1220168faf C++20 Round 6: range-for loops, throw() -> noexcept
- Convert 8 index-based for loops to range-for in main.cpp:
  CheckTransaction vout, GetValueIn vin, GetP2SHSigOpCount vin,
  ConnectInputs first pass vin, ConnectBlock vtx, Reorganize vConnect,
  block.vtx in CBlock::AcceptBlock
- Convert 4 loops in main.h: CTransaction::ToString vin/vout, CBlock::print vtx/vMerkleTree
- Convert checkqueue.h worker loop to range-for
- Convert script.cpp bitwise NOT loop to range-for
- throw() -> noexcept on 8 allocator constructors/destructors (C++17 removed throw())
2026-05-08 23:43:27 -07:00
sami7777 4b8d5ab8b1 C++20 Round 5: typedef -> using, IMPLEMENT_SERIALIZE macro cleanup
- Convert 15 typedef declarations to C++11 using aliases across 12 files:
  script.h (valtype, CTxDestination), serialize.h (CSerializeData),
  keystore.h (KeyMap, ScriptMap, CryptedKeyMap), sync.h (CCriticalSection,
  CWaitableCriticalSection), sync.cpp (LockStack), key.h (CPrivKey, CSecret),
  crypter.h (CKeyingMaterial), allocators.h (SecureString), main.h (MapPrevTx),
  wallet.h (mapValue_t, removed duplicate), miner.cpp (TxPriority),
  kernel.cpp (MapModifierCheckpoints)
- Remove redundant duplicate mapValue_t typedef in wallet.h
- IMPLEMENT_SERIALIZE macro: replace assert() warning suppression with
  [[maybe_unused]] attributes on fGetSize/fWrite/fRead/nSerSize
2026-05-08 23:08:40 -07:00
sami7777 9d80ddb6ac C++20 Round 4: enum class TxnOutType, remove boost string alg, boost::int64_t -> int64_t
- txnouttype -> enum class TxnOutType (NonStandard, PubKey, PubKeyHash, ScriptHash, MultiSig)
  Updated script.h/cpp, main.cpp, wallet.cpp, rpcrawtransaction.cpp, rpcwallet.cpp, multisig_tests.cpp
- boost::int64_t/uint64_t -> int64_t/uint64_t across all RPC files (7 files, 52 replacements)
- Replace all boost string algorithm includes with std:: equivalents:
  - Added TrimString(), ToLower(), ReplaceAll(), SplitString(), JoinStrings() to util.h
  - boost::trim -> TrimString() (bootstrap.cpp, trianglesrpc.cpp, rest.cpp)
  - boost::to_lower -> ToLower() (netbase.cpp, trianglesrpc.cpp)
  - boost::split/is_any_of -> SplitString() (rpcdump.cpp, trianglesrpc.cpp, rest.cpp)
  - boost::replace_all -> ReplaceAll() (main.cpp, wallet.cpp)
  - boost::algorithm::starts_with/ends_with -> std::string::starts_with/ends_with (rpcdump.cpp, smessage.cpp)
  - boost::algorithm::istarts_with -> case-insensitive lambda (init.cpp, qtipcserver.cpp)
  - boost::algorithm::join -> JoinStrings() (util.cpp)
- boost::bind -> lambda in trianglesrpc.cpp async_accept handler
- IsHex() parameter: const string& -> string_view
2026-05-08 22:26:31 -07:00
sami7777 150828b806 C++20 modernization: nullptr, constexpr, smart pointers, thread safety, enum class
- Replace ~320 NULL occurrences with nullptr across 47 files (-184 net lines)
- static const -> constexpr for version, coin, utility constants
- Collapse 9 PushMessage overloads into 1 variadic template with fold expressions
- Convert boost::array -> std::array, boost::type_traits -> std:: equivalents
- pwalletMain, pScriptCheckQueue, pScriptCheckThreads -> unique_ptr
- mapOrphanBlocks values: raw CBlock* -> unique_ptr<CBlock>
- Fix data races: add locks to wallet registration, pindexBest reads, mempool exists
- pwalletdbEncryption: raw new/delete -> local unique_ptr, remove exit() calls
- PoS reward overflow: CBigNum intermediate for nCoinAge * nRewardCoinYear
- memset -> OPENSSL_cleanse for secure zeroing
- Fix const-cast UB in SetMerkleBranch
- Log silent catch(...) blocks instead of silently swallowing
- Enum class: GetMinFeeMode, WalletFeature
- std::string_view for 8 utility function parameters
- Range-for with structured bindings: 63 iterator loops modernized
- std::make_pair -> brace init: 35 sites
- Delegating constructors: CWallet, CBlockIndex
- Merkle tree caching, std::array for GetMedianTimePast
- CScript copy ctor -> = default, operator!= -> = default
2026-05-08 21:34:24 -07:00
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 b4308e42ad Smoke-test the Krystie loop runner
Krystie Gate / Static gate (red-list / test-first / no-clearnet) (push) Successful in 35s
Krystie Gate / Build + ctest (push) Successful in 7m11s
Krystie Gate / Auto-merge to master (push) Successful in 28s
This issue was created to exercise the autonomous runner end-to-end.

**Acceptance:** runner picks this up, demo worker appends a line to docs/krystie-runner-log.md, branch krystie-wip/triangles_v5-1 is pushed, gate fast-forwards to master, this issue auto-closes.

Closes #1
Refs: krystie-wip/triangles_v5-1
2026-04-28 23:57:31 -07:00
Sami c4656ac244 fix: gate auto-merge — use git push instead of PATCH /branches/master
Gitea PATCH /repos/{owner}/{repo}/branches/{branch} is for renaming branches, not for moving refs; it always returned failure even when master had not diverged. Replace with a plain git push (token in extra header) which fast-forwards iff the update is FF-clean — same safety, correct mechanism.
2026-04-28 23:56:51 -07:00
sami7777 2da1c039a8 Gate: accept Krystie subkey ID (git %GK returns subkey not primary) 2026-04-28 22:42:22 -07:00
sami7777 2b701f6640 Gate: handle force-push orphaned-history (fall back to head-only inspection) 2026-04-28 22:39:14 -07:00
sami7777 de4498b8eb Fix gate: trust Krystie public key after import so %GK verifies 2026-04-28 22:35:51 -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 815cc02aa9 Bootstrap Krystie autonomous gate (manual one-time setup)
Adds the gate workflow + check script + Krystie public key under .gitea/.
This commit is intentionally unsigned so the gate treats it as an admin
bootstrap rather than a Krystie commit (which the gate would otherwise
require to land via krystie-wip/* + auto-merge).

After this, master branch protection will be enabled requiring the
Krystie Gate workflow to pass on all future pushes. Krystie will push
to krystie-wip/<task-id> branches and the workflow auto-merges on green.

See: krystie-buildout/workflows/* in the krystie repo for sources.
2026-04-28 21:42:57 -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
sami7777 f13e512712 M1.2 + parallel work: switch CTxDB& signatures to CTxDBBase&; snapshotnet, version bump
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
M1.2 (mechanical):
  Convert every CTxDB& parameter and reference across main.{h,cpp},
  wallet.{h,cpp}, and smessage.cpp to CTxDBBase&. Local instantiations
  like `CTxDB txdb("r");` are deliberately left as concrete LevelDB —
  they'll move behind a factory in M1.4 once the parity harness exists.

  CTxDB IS-A CTxDBBase, so all existing call sites continue to compile:
  a CTxDB instance binds to a CTxDBBase& parameter automatically.
  Forward declaration `class CTxDB;` in main.h replaced with
  `class CTxDBBase;`.

Parallel work (snapshotnet + version bump to 5.9.4 + checkpoints/init
/protocol/version edits) included so origin/master matches the local
working tree in one push.

NOT YET COMPILE-TESTED: pushed at the user's explicit request before
the build verification step. If CI fails, expected breakage is in
files that include main.h transitively but not txdb-base.h — fix is
to add `#include "txdb-base.h"` (or rely on the existing txdb.h which
pulls it in).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:52:12 -07:00
sami7777 b28525057a M1.1: Extract CTxDBBase abstract storage interface
First step of the multi-phase chaindb modernization plan. Introduces a
backend-agnostic abstraction over the chain database:

  * CTxDBBase — abstract class owning all serialization and named
    operations (ReadTxIndex, WriteBlockIndex, ReadAddressBalance, etc.).
    Templated Read/Write/Erase/Exists dispatch to byte-level virtuals
    (ReadRaw/WriteRaw/EraseRaw/ExistsRaw) so every backend produces
    bit-identical key bytes — required for migration and dual-backend
    parity testing later.

  * CTxDBIteratorBase — abstract iterator. Backends implement Seek,
    Valid, Next, KeyStr, ValueStr.

  * CTxDB now inherits from CTxDBBase and only implements the byte-level
    I/O, batch lifecycle, NewIterator, and LoadBlockIndex (which still
    uses leveldb directly during the v3 dbformat upgrade — extracted to
    base in a later phase).

  * UTXO read-through cache moved to txdb-base.cpp under an anonymous
    namespace — backend-agnostic so RocksDB will get it for free.

  * GetAddressUtxos / GetAddressTxIds / SumUtxoValues moved to base,
    using NewIterator() instead of pdb->NewIterator().

No call-site changes — every existing CTxDB user keeps working exactly
as before. Stack allocations like `CTxDB txdb("r")` still work because
CTxDB remains a concrete, cheap-to-construct class. Behavior is
bit-identical: same key serialization, same batch semantics, same
LoadBlockIndex flow.

Sets up M1.2 (factory + caller conversion to CTxDBBase&) and M1.3
(RocksDB backend) — neither requires touching consensus paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:37:45 -07:00
sami7777 b9d631e968 Ignore build artifacts and compiled Qt translations
Adds patterns for stray build directories, build error logs (including
the corrupted-name redirect file), and *.qm. Existing tracked .qm files
remain tracked; this only stops freshly-compiled regenerations from
cluttering git status.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:48:33 -07:00
sami7777 d5473d7cae Drop BUG_ANALYSIS_IBD_STALL.md
Companion to the prior cruft-doc cleanup. The fix it analyzed was
superseded by the comprehensive header-sync refill/watchdog work
already in main.cpp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:17:24 -07:00
sami7777 cd51ba41d8 Remove stale AI-generated documentation
Drop seven planning/strategy/upgrade-notes docs that have outlived their
usefulness, plus the dangling CODEX-TOR-GUIDE.md reference in
tor_embedded.cpp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:16:32 -07:00
sami7777 aef95bdf78 Bump version to v5.9.3 and add RPC command reference
Pairs net.h heartbeat-throttle field with the IBD header-sync fix
in 2a484e4, and ships a full RPC reference doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:16:19 -07:00
sami7777 f633b9e330 Fix IBD header sync refill and watchdog 2026-04-24 22:16:03 -07:00
Krystie e7c5c6596a Merge fix/recalculate-supply-chainwalk into master: IBD stall fix + supply recalculation
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-24 15:03:42 -07:00
sami7777 16b35f6b2b Fix IBD stall from header-sync cache exhaustion (v5.9.2)
Nodes syncing from zero would accept blocks normally up to ~6000 then
stall permanently with askfor_queue=0 and no new blocks. Root cause: a
broken feedback loop between the header planner and block downloader.
Blocks consume entries from mapHeaderSync (MAX 15000) while getheaders
refills only 2000 at a time; when the cache drains, hashBestHeaderSync
falls to 0 and every refill site is guarded on it being non-zero, so
the pipeline deadlocks with no recovery path.

Recovery paths added:

- ProcessBlock: when the cache is empty during IBD after accepting a
  block, broadcast getheaders to all full-node peers. This restarts
  the planner at the exact point it dies.
- Stall detection: send getheaders alongside the existing getblocks.
  getblocks alone cannot refill the header cache.
- SendMessages: belt-and-suspenders, re-request headers every 30s
  while hashBestHeaderSync == 0 in IBD, independent of stall state.

Also fix a secondary issue: GetHeaderSyncDownloadPath walks back from
the tip and breaks on the first TTL-evicted entry. The accumulated
partial tail has a parent that is neither in mapBlockIndex nor
mapHeaderSync, so requesting those blocks would produce orphans.
Discard the partial path on a gap; the recovery paths above will
re-request the missing range.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 11:48:35 -07:00
Krystie 7faf13dc31 Fix IBD stall: refill header cache when exhausted during sync
Two fixes for the header cache exhaustion bug:

1. Block-accepted path: when hashBestHeaderSync==0 and we're still
   behind peers during IBD, send getheaders to all peers to refill
   the header cache. Previously the refill was gated on
   hashBestHeaderSync!=0, creating a dead loop once the cache drained.

2. Stall detection: also send getheaders alongside getblocks when
   a stall is detected. Previously only getblocks was sent, which
   cannot refill mapHeaderSync or restart the header planner.

Root cause: getheaders returns 2000 headers per batch. Blocks are
consumed from the cache faster than headers are fetched. Once
mapHeaderSync empties, hashBestHeaderSync becomes 0, and the
refill path is never taken again.

See BUG_ANALYSIS_IBD_STALL.md for full details.
2026-04-24 11:21:39 -07:00
Krystie db65324b7a Add IBD stall bug analysis: header cache exhaustion without refill 2026-04-24 11:20:18 -07:00
sami7777 c98bdbe335 Harden recalculatesupply: MoneyRange gate, atomic apply, single-walk (v5.9.1)
Follow-up to #5. Addresses three risks with the apply=true path:

- MoneyRange sanity gate: refuse to persist a recalculated supply that is
  negative or above MAX_MONEY (2,222,222 TRI). A walk that produces an
  out-of-range figure indicates a bug (orphan contamination, missing
  prevout), not real chain state. Prevents corrupting nMoneySupply with
  junk values.
- Atomic apply: wrap every per-block WriteBlockIndex in a single
  TxnBegin/TxnCommit so a mid-walk failure leaves on-disk state
  untouched instead of half-rewritten.
- Single chain walk: cache (valueOut - valueIn) per block during the
  dry-run pass and reuse the cached deltas during apply. Previous code
  walked the full chain twice, roughly doubling apply runtime on a
  2.2M-block chain.

Help text now warns that the RPC holds cs_main for the full walk and
blocks new blocks, wallet ops, and other RPC for the duration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:56:33 -07:00
SamiAhmed7777 6f1227b022 Merge pull request #5 from SamiAhmed7777/fix/recalculate-supply-chainwalk
Add full-chain supply recalculation RPC
2026-04-23 18:51:55 -07:00
Krystie 12205cdc37 Add full-chain supply recalculation RPC
Rebuild money supply by walking the active chain from genesis and
summing block valueOut - valueIn, instead of relying only on current
UTXO totals. Optionally persist repaired nMoneySupply values across the
active chain with apply=true.

This helps repair corrupted money-supply tracking after chain/index
incidents and exposes both recalculated chain supply and UTXO supply for
comparison.
2026-04-23 18:50:14 -07:00
SamiAhmed7777 2fc0e8155a Merge pull request #4 from SamiAhmed7777/update-explorer-url
Update block explorer URL on Qt wallet Overview page
2026-04-23 18:23:25 -07:00
Krystie eeda728564 Update block explorer URL to blocks.cryptographic-triangles.org
Replace the old explorer.triangles.technology link on the Qt wallet
Overview page with the new self-hosted block explorer at
https://blocks.cryptographic-triangles.org
2026-04-23 18:22:05 -07:00
sami7777 0df054bbcb Build acceleration: ccache, unity build, precompiled headers
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
- Auto-detect and use ccache as compiler launcher when available
- Add ENABLE_UNITY_BUILD option for jumbo builds (batch size 8)
- Precompile heavy STL/Boost/OpenSSL headers for C++ targets
- Exclude hash9 crypto from unity builds (colliding static symbols)
- Fix RAND_screen() compile error on OpenSSL 3.x (removed API)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 13:28:02 -07:00
sami7777 fbd931a392 Network stability & connectivity hardening (v5.9.0)
- BIP 31 ping/pong with 2-min heartbeat, RTT tracking, 3-miss disconnect
- Reduce max outbound from 16 to 8, add -maxoutbound flag
- Emergency reconnection: 15s re-seed when 0 peers, 30s when 1 peer
- Inactivity timeout reduced from 90min to 10min (dead peer detection)
- Header sync TTL extended from 5min to 15min for Tor latency
- Reserve 2 inbound slots for known seed nodes at capacity
- Enhanced address gossip: hourly rebroadcast, getaddr from all peers
- New getnetworkstability RPC with isolation risk assessment
- getpeerinfo now includes pingtime, blocksdelivered, avglatency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 02:52:33 -07:00
sami7777 a792f90489 Fix linker error: move extern txdb declaration out of UtxoSnapshot namespace
The extern declaration for the global leveldb::DB *txdb was inside
namespace UtxoSnapshot{}, causing the linker to look for
UtxoSnapshot::txdb instead of the global ::txdb defined in
txdb-leveldb.cpp.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 02:39:37 -07:00
sami7777 64939a9793 Sync & relay improvements: compact blocks, sendheaders, adaptive timeouts (v5.8.8)
7 sync/relay optimizations for faster block propagation on Tor-only network:

1. Improved unsolicited block push: track nBestKnownHeight from inv/block
   messages instead of static nStartingHeight, so peers that sync up
   receive direct block pushes
2. Reduced redundant-request timeout from 20s to 5s for faster failover
3. Pipeline improvement: continuous download window refill after every
   accepted block + refill interval reduced from 5000 to 500 blocks
4. Sendheaders (BIP 130-style): negotiate header-based block announcements
   to save one round-trip vs inv->getdata->block
5. Compact block relay: send header + prefilled coinbase/coinstake + short
   tx IDs. For typical PoS blocks (0-2 txs) this is the complete block
   with no follow-up needed. Includes getblocktxn/blocktxn for missing txs
6. Adaptive peer timeouts: use rolling average latency (EMA 7/8) to set
   per-peer request and stall timeouts instead of fixed constants
7. Dual-peer requesting during IBD: request each block from two peers
   simultaneously, use whichever arrives first

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 02:23:32 -07:00
sami7777 b506a48192 UTXO snapshot support, script verify cache, and Tor sync tuning (v5.8.7)
- Add UTXO snapshot dump/load system (utxosnapshot.cpp/h) for fast initial sync
- Add dumputxoset RPC command to create snapshots from current chain state
- Add script verification cache (sigcache.h) to skip re-verifying scripts
  already validated during mempool acceptance
- Bootstrap: try UTXO snapshot first (fast path), fall back to full bootstrap
- Support manual utxo-snapshot.bin loading on startup
- Tune sync parameters for Tor: increase timeouts, reduce buffer sizes
- Header sync cache: TTL-based eviction instead of full cache clear
- Reduce orphan block limits and script check batch size for lower memory usage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 01:17:51 -07:00
sami7777 dee0d9ef62 Fix Tor process cleanup: kill orphans on startup, Job Object on Windows
- Add Windows Job Object (JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) so Tor
  child process is automatically killed when the wallet exits for any
  reason (crash, Task Manager, clean shutdown)
- Replace port-reuse "assume running" path with active orphan cleanup:
  Windows enumerates and kills tor.exe processes, Linux uses PID file
- Move deep-reorg trust-delta check into Reorganize() so short forks
  (<=6 blocks) converge freely while long-range attacks are still blocked

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 22:05:09 -07:00
sami7777 22e220acaa Anti-fork hardening + checkpoint update (v5.8.6)
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
- Add checkpoints through block 2,209,000 to lock canonical chain
- Ban peers on incompatible forks (no common blocks after 3 getblocks)
- Auto-checkpoint: finalize blocks at MAX_REORG_DEPTH to prevent deep reorgs
- Require 10% trust delta for side-chain reorgs (first-seen advantage)
- Add gencheckpoints RPC command for easy future checkpoint generation
- Add wallet onion address to hardcoded seed list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 18:14:58 -07:00
sami7777 1c068f4782 Sync speed + anti-fork hardening (v5.8.5)
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
- In-memory UTXO cache (2M entries, read-through with negative caching)
- Signature cache upgrade (unordered_set, 200K entries, 64-bit compact keys)
- Speed-weighted peer block assignment (fast peers get more blocks)
- 500-block max reorg depth (finality limit post-IBD)
- 7-day coin age soft cap (prevents stake surprise attacks)
- Timestamp tiebreaker for equal-trust fork resolution
- 30s stake cooldown after orphaned block (reduces fork oscillation)
- Slow-peer eviction (disconnect 0-block peers after 3min during sync)
- Only push new blocks to near-tip peers (within 10 blocks)
- Smart orphan eviction (FIFO oldest-first instead of random)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 11:55:18 -07:00
sami7777 a671708f0b Anti-fork hardening: faster convergence for small Tor-only network (v5.8.3)
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
- Reduce equal-trust reorg cooldown from 10min to 2min for faster convergence
- Tighten future block drift from 3min to 90sec to shrink competing-block window
- Require 2+ peers before staking (was 1) to prevent isolated fork creation
- Push full blocks directly to peers instead of inv-only (saves 1-2s Tor roundtrip)
- Add periodic 45-second chain-tip sync to detect and resolve silent forks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 19:32:52 -07:00
sami7777 be90d39cd4 Add direct TCP bootstrap downloads and HTTP redirect handling
Bootstrap server is on clearnet, so bypass Tor SOCKS proxy for faster
downloads. Adds redirect following (301/302/307/308) with safety limits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 18:11:46 -07:00
sami7777 4d0478add5 Fix build error: fWalletUnlockStakingOnly is a global variable, not CWallet member 2026-04-19 02:41:42 -07:00
sami7777 734979c93b Fix critical stability issues (v5.8.2 stability patch)
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
Critical fixes for production stability:

1. NULL POINTER CRASH FIXES (P0)
   - Add defensive null checks in GetNextTargetRequired_()
   - Fix GetDifficulty() crash when no PoW blocks exist
   - Prevents seed node crash-loops and RPC failures

2. CHAIN REORGANIZATION ATOMICITY (P0)
   - Move setStakeSeen modifications to AFTER database commit
   - Prevents DB/memory state desync on failed reorgs
   - Adds critical transaction boundary documentation
   - Improves reorg logging with fork depth details

3. ORPHAN BLOCK MEMORY MANAGEMENT (P1)
   - Extract LimitOrphanBlocks() into reusable function
   - Add proactive cleanup when IBD completes (4000→2000 limit)
   - Prevents memory exhaustion DoS attacks
   - Better diagnostic logging

4. DATABASE ERROR HANDLING (P2)
   - Enhanced critical error messages in TxnCommit()
   - Clear guidance on disk/corruption/permissions issues
   - Faster incident diagnosis

All changes are consensus-safe with no fork risk.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-19 02:31:55 -07:00
sami7777 00af636aca Update seed nodes and enable parallel block downloads
- Updated README.md with current onion seed nodes
- Increased header download window from 128 to 512
- Added parallel block downloading across multiple peers
- Improved sync performance with redundant request timeouts

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-19 02:09:28 -07:00
sami7777 9377b3a52f Release v5.8.2: Anti-fork fixes, staking diagnostics, and performance improvements
Version System:
- Unified version display as v5.8.2 (removed trailing .0)
- Single source of truth in clientversion.h
- Fixed version.cpp to use CLIENT_VERSION_* macros

Staking Improvements:
- Enhanced getstakinginfo with detailed diagnostics
- Shows specific reasons when staking is disabled
- Added wallet lock status, mature coins check, peer count

Performance & Sync:
- Added checkpoint at block 2,200,000 (hash: 0a8d0442...)
- 14 total checkpoints for faster sync
- Enhanced recalculatesupply RPC with safety validation
- Prevents changes > 1M TRI, fixes money supply tracking

Anti-Fork Protection:
- Enhanced reorganize logging with fork details
- Shows old/new tips, fork point, disconnect/connect counts
- Works with existing anti-oscillation and chain re-eval fixes

Recovery Tools (Krystie):
- -reindex flag for full block index rebuild
- recalculatesupply RPC to fix money supply from UTXOs
- SumUtxoValues() helper for UTXO set analysis

All changes are non-consensus and wallet-safe.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Krystie <krystie@cryptographic-triangles.org>
2026-04-19 02:09:12 -07:00
Krystie 1881ff867e Auto-backup wallet.dat before flush/rewrite operations
- Add AutoBackupWallet() that copies wallet.dat to wallet.dat.auto.bak
  before any DB flush or rewrite
- Call AutoBackupWallet() in ThreadFlushWalletDB() before flushing
- Call AutoBackupWallet() in AppInit2() after loading wallet
- Add suspicious-size check in AppInit2() (warns if wallet.dat < 1KB)
- Declare AutoBackupWallet() in db.h

This protects against wallet corruption during crash by maintaining
an auto-backup that is always at least as recent as the last flush.
2026-04-18 23:34:19 -07:00
Krystie caddfb1789 Add checkpoints to 2.2M+, bump orphan limit to 2000, add modernization roadmap
- Add mainnet+testnet checkpoints at blocks 2190000, 2200000, 2205000
- Bump MAX_ORPHAN_BLOCKS from 750 to 2000 (prevents fork deadlocks)
- Add MODERNIZATION_ROADMAP.md with prioritized improvement plan

These changes prevent the exact fork deadlock that happened during
the Apr 17-19 incident: post-IBD orphan limit of 750 was too low,
causing nodes to deadlock when divergent blocks arrived.
2026-04-18 19:30:37 -07:00
sami7777 6eb25d6b25 Add RPC commands, systemd service, and operational docs
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
New RPC commands:
- addnode: add/remove/onetry .onion peers at runtime
- disconnectnode: immediately drop a peer connection
- getchaintips: diagnose chain forks and orphan branches
- invalidateblock: rewind chain past a bad block
- reconsiderblock: re-activate a previously invalidated block

Also includes:
- systemd service files for Linux deployment
- Bootstrap/snapshot guide for OpenClaw nodes
- Upgrade notes from 2026-04-14

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 01:17:32 -07:00
sami7777 cd7b68f7cb Fix null pointer crashes causing seed node crash-loops (v5.8.1)
Guard pindexBest and pprev dereferences that segfault during IBD
block serving when chain state is incomplete:
- kernel.cpp: CheckStakeKernelHash null pindexBest during PoS validation
- main.cpp: InvalidChainFound null pprev/pindexBest on rejected blocks
- main.cpp: SetBestChain null pprev in trust calculation
- main.cpp: ProcessBlock orphan handler null pindexBest

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 00:55:48 -07:00
sami7777 a0e8e74d0d Remove FALLBACK_HOST reference from introdialog.cpp (fix Qt build)
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
The clearnet fallback host was removed from bootstrap.h in the previous
commit but introdialog.cpp still referenced Bootstrap::FALLBACK_HOST.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 19:34:46 -07:00
sami7777 7d62e34868 Harden network security, fix moneysupply tracking, version system overhaul (v5.8.0)
- Fix moneysupply calculation in FastImportBlockFile and ConnectBlock assumevalid path
- Route bootstrap downloads through Tor SOCKS proxy (no more clearnet leaks)
- Remove hardcoded clearnet fallback IP from bootstrap
- Fix snprintf missing argument in walletmodel.cpp narration key (UB/crash)
- Fix potential null deref from db_strerror() in rpcwallet.cpp
- Filter non-.onion addresses from HTTPS seed list parser
- Add periodic re-seeding when node has 0 outbound peers
- Make clientversion.h single source of truth for version display string
- Remove redundant DISPLAY_VERSION macros from version.h
- Update README: max supply 2,222,222, CMake build instructions, Tor-only config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 19:26:12 -07:00
sami7777 b0e9ca334f Use deterministic time check in CheckBlock to fix Tor chain splits (v5.7.9)
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
Replace FutureDrift(GetAdjustedTime()) with GetTime() + 15min in CheckBlock
and header-sync validation. GetAdjustedTime() incorporates peer-reported
time offsets that vary between Tor nodes, causing the same block to be
accepted by some nodes and rejected by others — the primary cause of
persistent chain forks. AcceptBlock still enforces tight 3-min drift rules
deterministically against the previous block timestamp.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 23:54:16 -07:00
149 changed files with 12839 additions and 5193 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
+65
View File
@@ -0,0 +1,65 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGnxdoUBEACaICSRk5Clg4kI5IubMXnXLbsSWzi0TKIpqh4Tqgl2k1bgSxda
tuBabHcsaw6Kpo96CJl9aZ63VIrEhCSdirGm/wWlbnTvm6cK4EDucGgS4BdEfm9B
Lw2c+iTjuJqJt2HLbRkZmF8qHy0Mo1DjsjbWUiwIP62RkuxCNuW2Wl9euak504UW
ZTFB9f3Bu1C6rknsWQ0VR5HJwWN4UrVMukZhvlzLRjKgW7W2XchSXUIAe7b0/5jo
pFB30pwxbaBIoeJu8AHYnzBYRThp0WbDTC/LK5FSnSgG751jOtkbheRNGjO65a2L
gkaclxo1NUIIu+WqdBtTbpUQM7UEd50FOXxUgq/xJhGujNMJyOMMPEzfJ+kP9pD4
p+gkNCLLgvT+gu1PnF0iTIAb4qggHGzZGRgc5lTxC28XEud0DAx+Pdcdf/nlQTsu
AOjZZgiiLIjwJZo/RYwId1Wh+LmtYZqVZ6j4vqqaXXPADpN40LGyUo376+oVSn77
1w2j1CWSmTEPaq4KmvTvnTvFfbeXkKckmUziBYwqZI0uA2xE6ShNUaAS4kdIaZhO
Bb3t9xrwu2QAR1rRlNTCChOyNbauvo32GLRnXg5BXYTBsmMU/QHe6EBJsycq/IHl
2yNPQUtynxzkDZ9OYrwbZaTZOCJK0pHwm4HUmV3rPiEPXUKJXXDojWQYpwARAQAB
tHlLcnlzdGllIFRyaWFuZ2xlcyBSZWxlYXNlIChBdXRvbm9tb3VzIHJlbGVhc2Ug
c2lnbmluZyBrZXkgZm9yIHRyaWFuZ2xlc192NSkgPGtyeXN0aWUtdHJpYW5nbGVz
LXJlbGVhc2VAZG5zMi5zYW1pLnRhaWxuZXQ+iQJYBBMBCgBCFiEEUjqBgz63IBVz
4e/h3PJXmWgQeYQFAmnxdoUDGy8EBQkDwmcABQsJCAcCAiICBhUKCQgLAgQWAgMB
Ah4HAheAAAoJENzyV5loEHmEPm0P/3y2Y5Y1rhgSj6yN/1PuXhpp1sNqXBOJZxTW
uUx/4LUqLgqbtFC0fR4BwpTYEkGGaofi0/95sPwKu0jmVR6hJ+8Omk/4TMRmXUYq
JUTA0/xzj9sOndaqiwRY3Y/YO/ytahL89y8xl5cYSaOOwLI/f9xo8pq1t20Iiuiw
kcaUBRQgpTVMI49VcXwrEUMnjV9cldGqql8v7CSKds5rRxQgT8ifaC6euTWxK0Tn
5Yu/wnBd+akU5/bcI8PEp5VyUyAJMZJPZ6mUqriWXlnhiUj0NawEKtfG9qlkMixL
5ujz9lu/9MvFUYC4QSvcd1O3k9MJ6T4Yk/uEygEca8Y/3DcccWRMHjW2Ah+ewhHE
yHy0tctzCe7pco+jfB7zicKv0bjXarvwBZ43e5F/zG5PMpo0XAS9EkEUV+/9BJ38
jBHvzqwXsYTnxS0hgOSONJk9Cc6i0NN1ex3rPOrYvBvHWZ+9n3AU2taUljuypDGO
RweCHsFMYGx/oOI94bD7wTeVey0tAZ+3Urz6T5qY5SmNKiwZ5NtbYo0Mp8r5DdPJ
N9KtXtaDMPI/rORjl1Ad9xhDbGMCr7EH9SjTU+z51me31/ZU58jICGlvm3/JDcb5
CAWyDppvW0ul9yqo1fecSi3w7m2sI+4F+tj8oLFmO+5rQw85F4LPqjVVMbUUkoAH
udtoU3Y8uQINBGnxdoUBEACtFpgwuwEZqxbsfmL+uBxHnxSSRm2vlQc7HRtQG6Nu
Tg1x4s9xFO6kNkcslPgZx9XSvFkPt1RUCNViTYE34UoOfkBs+aNkw4ztwuKGt/AS
CZFRX99yBx7P0kiV4Nt/Cj3oQBtEXQixMmGK4+N0WBskV/QxRFA7hl+ZQBeEFsYP
15UyjX2h6HFRYTSPKufEmtE/OkO9dg3fyxTvZ3+1o3eWWjT4VReX4jvmzXn3RNP1
BwuAy+iwmnqUBcuEZ0qQiT/+oRLCHOFLCAjVoSsPY9WJfF67XpDb2noV/0RqltMD
jUc/MT8Bxn/y8qHKvQuyPms/YO5jMI7q+/D1eayO4R48qhsMVp6Rjb31xalMWT2W
rwQg1XaFG80vUisbfX6CU0sH34tWQkqAL7AiwradPtwB0Sn60Em5UgHdWQ7rkd+h
mFOUjYi3Q1hOuPQNuzDK51n5sv8qOIrfghR0F2AtRkpbhBYM9435U+JkcZTjJ6wp
WYLBTAys4qo9MnL18Z4byaw4e122eBgI3/UOvG+7C7wIAwmiDvnYzqErz7iOmuTe
+cgdWYmLFvkfx8P6Ka+6likSV4ZY/ASP4Uo/gTspatwqHApAmphfVEGwm0/wKMl2
Br+zuZZ8RJ1GxahwJ1oo3uuGjIQjGNplh2wHVvbsfg4mlFKDbShdJ5adtx/E6BrT
NQARAQABiQRyBBgBCgAmFiEEUjqBgz63IBVz4e/h3PJXmWgQeYQFAmnxdoUCGy4F
CQPCZwACQAkQ3PJXmWgQeYTBdCAEGQEKAB0WIQRpE+E2EPaYGDQpziDC3GBhjIWh
WQUCafF2hQAKCRDC3GBhjIWhWQYID/0Ru2U9rLatIAjoSWI6TMFaOaxHf1NAsTcz
fPRbFNxx0d4ByjfjLlrfnDpQXsFpMa6/BpQ1Ps1ApW+wQsuHXxj/jdZVSi5f/sOT
XKZq/MRZu8enA1foj0b6sJ13ZWY0iIWmIeK8NWuNBFWz2QTjRie2hqoOTR+Hy43r
gRMlzPaXNoeD2UuvhoDphH2g2OWcppxd2b1yk7W9kh0CgvXXg4cPee71LmXLZMoL
GJcmtSkU24fiwa95TSk2J5qQ3voP5Knk8e/VgGmOSUoUzr+O5N6tEO2KPVr3bsFt
8zKHEyuddDYUju4U2Fl+xq4yJCYX3h6AKyh/c3bOAGp4f3zs62XPjn9RIXlTH9Lw
Vp97pJRzAEYzXRGXfGJRz54hQzft1L+BkhqWpVwzxI1fnflpVghahHOIoa0bnpyH
ycxxvkGY6o5TS5Ymqf4yry/4G+C64kX2GlBgmN2I2+UJ3z/cyEqY4XVMGk4S7uLq
d0eKrA2ZaSHUce0F/gGpMynxGFP+BNlfNBcSwzgBbnvcyFhOtls4LvTAcLmyBpjM
gEugtkskDSxJd/HcnTcFF5P9UcVPdD7vg7tlUXQ37AvbeppFC4pFbxYK01SOYk+W
nXH/Mq1XkFFcArVtsL1octAWuaqn8M/5kXnKvhw/TCBNPfQ7Kljx1V65kErMXNl2
F/cJXWQKCXPtD/92EXa9uvIxCINwxyZidwEvqx1xpBTIDDdYvDt8ZXHr957xpiaz
ls3aHy0mMUGigzVEL0AcPToBEudEzy+z1pB0y23znveycDZRTRsGnDwLrdb9eqTu
JDViRtB6WBASGsU3XHMYFietvEukmqJj55KCDl5YapZDKUb1iraERJ72PH9xk3C7
501Cklfe+GM8VBymwApOjWPLw1cIxVOL/Ex9ADsVMYDubAVh0LnqvDTg8e8bv4gu
BhyC2AXsQIUZ9HtixfvLZ6sdsPjstlQj+ZinpTHWthx52jrfcRYOo32cE06BpR3U
bQ+mjn6orzZ7Iq5p6aejukCddvlSX381vMaLf1/FGzmu/9f52p7uTLxU7N8sEcqq
PlkdRYatwWDeKuGpYVqmXuPvAaPD/sfH6zw0O5JjcNhb5KqTMjcV7IXV+V7QU2F5
iH5eYepAFf5uctffFMlCZ2YtCLlISMxHWLLqupIlu/JumTLcUjXUpOMV/sp+v6gD
66yx5QQWtVdYT9dYW+EUybjuWlS85T9DJVrPx5GiQfKjgFzuyuEvsbExzVBOwsBP
o/pPUWyBNSI6YVrm329U7ybAuDdnTveaMtIxRneN8mM9lhXNWpb8UpvSGnMP0lLI
tx58dQjEl3lbis897KDgzHy2pGKQDcvLdj14/xpfjeTWHI6Ut3mZylIKWg==
=zWaw
-----END PGP PUBLIC KEY BLOCK-----
+262
View File
@@ -0,0 +1,262 @@
#!/usr/bin/env python3
"""Krystie Gate — static check stage of the CI gate.
Runs inside the Gitea Actions runner. Inspects all commits that were just
pushed to a krystie-wip/* branch and rejects if any violates the gate rules.
Decision per commit:
* If signed by Krystie's GPG key (fingerprint DCF2579968107984), apply the
full per-repo gate.
* If signed by a different key OR unsigned, allow (Sami's authority).
Per-repo enforcement:
* triangles_v5 : red-list (consensus paths) + test-first + no-clearnet
* triangles-explorer, triangles-api, tridock-web-wallet, sami-chat, tri-pi:
test-first only
* homebrew-triangles: formula syntax check only
Outputs:
* On reject, prints REJECTED lines to stderr and exits 1.
* On accept, sets `is_krystie_commit` GH-actions output to true/false.
"""
from __future__ import annotations
import os
import re
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
# Krystie's GPG identity. We accept both the primary long-ID and the
# signing subkey because `git log %GK` returns the subkey that was actually
# used to sign, not the primary. The full primary fingerprint is also
# included so a paranoid future check can validate the chain.
KRYSTIE_PRIMARY_FP = "523A81833EB7201573E1EFE1DCF2579968107984"
KRYSTIE_KEY_IDS = {
"DCF2579968107984", # primary long-ID
"C2DC60618C85A159", # signing subkey long-ID
}
RED_LIST_TRIANGLES_V5 = [
re.compile(r"^src/main\.(cpp|h)$"),
re.compile(r"^src/validation.*"),
re.compile(r"^src/kernel\.(cpp|h)$"),
re.compile(r"^src/checkpoints\.(cpp|h)$"),
re.compile(r"^src/consensus/"),
re.compile(r"^src/protocol\.(cpp|h)$"),
re.compile(r"^src/net\.(cpp|h)$"),
re.compile(r"^src/netbase\.(cpp|h)$"),
re.compile(r"^src/net_bootstrap\.(cpp|h)$"),
re.compile(r"^src/chainparams.*"),
re.compile(r"^src/clientversion\.h$"),
re.compile(r"^src/key\.(cpp|h)$"),
re.compile(r"^src/keystore\.(cpp|h)$"),
re.compile(r"^src/onionseed\.h$"),
re.compile(r"^contrib/seeds/"),
re.compile(r"^contrib/devtools/release.*"),
re.compile(r"^doc/release-process\.txt$"),
]
TEST_DIRS = {
"triangles_v5": ["src/test/", "test/"],
"triangles-explorer": ["src/__tests__/", "tests/", "test/"],
"triangles-api": ["test/", "__tests__/", "tests/"],
"tridock-web-wallet": ["test/", "__tests__/", "tests/"],
"sami-chat": ["test/", "__tests__/", "tests/"],
"tri-pi": ["test/", "tests/"],
"homebrew-triangles": [],
}
SOURCE_EXTS = {
"triangles_v5": {".cpp", ".h", ".c"},
"triangles-explorer": {".ts", ".tsx", ".js", ".svelte"},
"triangles-api": {".js", ".ts"},
"tridock-web-wallet": {".ts", ".tsx", ".js", ".svelte", ".vue"},
"sami-chat": {".ts", ".tsx", ".js", ".svelte", ".vue"},
"tri-pi": {".py", ".sh", ".ts", ".js"},
"homebrew-triangles": set(),
}
RED_LIST_REPOS = {"triangles_v5"}
PEER_CONFIG_PATHS = [
re.compile(r"^contrib/seeds/"),
re.compile(r"^src/chainparams.*"),
re.compile(r".*triangles\.conf(\.example)?$"),
]
@dataclass
class GateResult:
ok: bool
reason: str = ""
def repo_name() -> str:
repo = os.environ.get("GITHUB_REPOSITORY", "")
return repo.split("/", 1)[1] if "/" in repo else repo
def commit_signer(sha: str) -> str | None:
try:
out = subprocess.run(
["git", "log", "-1", "--format=%GK", sha],
check=True, capture_output=True, text=True,
).stdout.strip()
return out or None
except subprocess.CalledProcessError:
return None
def is_krystie_commit(sha: str) -> bool:
fp = commit_signer(sha)
if not fp:
return False
# Accept any key ID we know belongs to Krystie. `git log %GK` returns the
# signing subkey, so we have to whitelist both primary and subkey.
return any(fp == known or known.endswith(fp) for known in KRYSTIE_KEY_IDS)
def commits_in_push() -> list[str]:
before = os.environ.get("GITHUB_BEFORE", "")
sha = os.environ.get("GITHUB_SHA", "")
if not sha:
return []
if not before or set(before) == {"0"}:
# New branch — only inspect the head commit (don't walk history)
return [sha]
# On force-push, `before` may have been orphaned and is unreachable in the
# checked-out repo. `git rev-list before..sha` then exits 128. Fall back
# to inspecting the new head only — that's the safest guarantee we can
# make about what just landed.
try:
out = subprocess.run(
["git", "rev-list", f"{before}..{sha}"],
check=True, capture_output=True, text=True,
).stdout
return [c for c in out.split() if c]
except subprocess.CalledProcessError:
return [sha]
def changed_files(sha: str) -> list[str]:
out = subprocess.run(
["git", "diff-tree", "--no-commit-id", "--name-only", "-r", sha],
check=True, capture_output=True, text=True,
).stdout
return [f for f in out.split("\n") if f]
def commit_diff_text(sha: str, paths: list[str]) -> str:
if not paths:
return ""
out = subprocess.run(
["git", "show", "--no-color", sha, "--"] + paths,
check=True, capture_output=True, text=True,
).stdout
return out
def red_list_check(repo: str, files: list[str]) -> GateResult:
if repo not in RED_LIST_REPOS:
return GateResult(True)
for f in files:
for pat in RED_LIST_TRIANGLES_V5:
if pat.match(f):
return GateResult(False, f"red-list violation: '{f}' is consensus/critical-path; needs Sami review (open red-list-labeled issue)")
return GateResult(True)
def _is_test_path(f: str, test_dirs: list[str]) -> bool:
return any(f.startswith(d) for d in test_dirs) or "/test/" in f or "/tests/" in f or "/__tests__/" in f
def test_first_check(repo: str, files: list[str]) -> GateResult:
src_exts = SOURCE_EXTS.get(repo, set())
test_dirs = TEST_DIRS.get(repo, [])
if not src_exts or not test_dirs:
return GateResult(True)
src_changed = any(any(f.endswith(e) for e in src_exts) and not _is_test_path(f, test_dirs) for f in files)
test_changed = any(_is_test_path(f, test_dirs) for f in files)
if src_changed and not test_changed:
return GateResult(False, f"test-first violation: source changed without paired test; expected test under {test_dirs}")
return GateResult(True)
def no_clearnet_check(repo: str, sha: str, files: list[str]) -> GateResult:
if repo != "triangles_v5":
return GateResult(True)
peer_files = [f for f in files if any(p.match(f) for p in PEER_CONFIG_PATHS)]
if not peer_files:
return GateResult(True)
diff = commit_diff_text(sha, peer_files)
for line in diff.split("\n"):
if not line.startswith("+") or line.startswith("+++"):
continue
body = line[1:].strip()
if re.search(r"\b(addnode|seednode|connect)\s*=", body, re.IGNORECASE):
if ".onion" not in body.lower():
return GateResult(False, f"no-clearnet: added peer/seed without .onion: {body[:120]}")
if re.match(r"^\s*(\d{1,3}\.){3}\d{1,3}\b", body) or re.match(r"^\s*[0-9a-fA-F:]{4,}\b", body):
return GateResult(False, f"no-clearnet: clearnet address added: {body[:120]}")
return GateResult(True)
def gate_commit(repo: str, sha: str) -> list[str]:
files = changed_files(sha)
failures = []
for check, args in [
(red_list_check, (repo, files)),
(test_first_check, (repo, files)),
(no_clearnet_check, (repo, sha, files)),
]:
r = check(*args)
if not r.ok:
failures.append(f"commit {sha[:12]}: {r.reason}")
return failures
def emit_output(name: str, value: str):
out_file = os.environ.get("GITHUB_OUTPUT", "")
if out_file:
with open(out_file, "a") as fh:
fh.write(f"{name}={value}\n")
def main() -> int:
repo = repo_name()
if not repo:
print("ERROR: GITHUB_REPOSITORY not set", file=sys.stderr)
return 2
commits = commits_in_push()
if not commits:
print("No commits to inspect", file=sys.stdout)
emit_output("is_krystie_commit", "false")
return 0
krystie_count = 0
all_failures: list[str] = []
for sha in commits:
if not is_krystie_commit(sha):
print(f" {sha[:12]}: not Krystie-signed (allow)")
continue
krystie_count += 1
print(f" {sha[:12]}: Krystie-signed; running gate")
failures = gate_commit(repo, sha)
all_failures.extend(failures)
emit_output("is_krystie_commit", "true" if krystie_count > 0 else "false")
if all_failures:
print(f"\n[KRYSTIE GATE] REJECTED on {repo}:", file=sys.stderr)
for f in all_failures:
print(f" - {f}", file=sys.stderr)
return 1
print(f"[KRYSTIE GATE] PASS on {repo} ({krystie_count} Krystie commit(s) inspected, {len(commits) - krystie_count} non-Krystie)")
return 0
if __name__ == "__main__":
sys.exit(main())
+132
View File
@@ -0,0 +1,132 @@
name: Krystie Gate
# Runs on every push to krystie-wip/* branches.
# Static checks first (cheap), then build + tests.
# If everything green AND the commit is Krystie's, fast-forwards master.
# Sami's pushes (admin) bypass this entire flow — he goes direct to master.
on:
push:
branches:
- 'krystie-wip/**'
jobs:
static-gate:
name: "Static gate (red-list / test-first / no-clearnet)"
runs-on: ubuntu-latest
outputs:
is_krystie_commit: ${{ steps.gate.outputs.is_krystie_commit }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Import Krystie public key (for verification)
run: |
mkdir -p ~/.gnupg && chmod 700 ~/.gnupg
if [ -f .gitea/krystie-release.pub.asc ]; then
gpg --import .gitea/krystie-release.pub.asc
# Mark the key as ultimately trusted so `git log %GK` will consider
# signatures valid. Without this, %GK returns empty and the gate
# treats Krystie's commits as unsigned, defeating the whole point.
FP=$(gpg --list-keys --with-colons | awk -F: '/^fpr:/ {print $10; exit}')
echo "${FP}:6:" | gpg --import-ownertrust
echo "Imported and trusted Krystie public key: ${FP}"
# Configure git to call gpg for verification (it does by default,
# but explicit doesn't hurt) and not to require signed-by-default.
git config --global gpg.program gpg
else
echo "WARN: .gitea/krystie-release.pub.asc not found — gate will treat all commits as non-Krystie (i.e. allow)"
fi
- name: Run gate
id: gate
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_SHA: ${{ github.sha }}
GITHUB_BEFORE: ${{ github.event.before }}
run: |
python3 .gitea/krystie_gate.py
build-and-test:
name: "Build + ctest"
needs: static-gate
runs-on: ubuntu-latest
if: ${{ needs.static-gate.result == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential cmake ninja-build pkg-config \
libssl-dev libboost-all-dev libdb++-dev libleveldb-dev \
librocksdb-dev libevent-dev libsodium-dev \
libsecp256k1-dev || true
# Some packages may not be available; the C++20 / RocksDB modernization
# is in flight, so missing deps are tolerable for v1 of the gate.
- name: Configure (daemon-only, no Qt)
run: |
mkdir -p build && cd build
cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_QT=OFF \
-DBUILD_TESTS=ON \
-DBUILD_ROCKSDB=OFF \
|| (echo "::warning::CMake configure failed — likely WIP modernization. Allowing build skip for v1." && exit 0)
- name: Build
run: |
if [ -f build/build.ninja ]; then
cd build && ninja -j$(nproc) 2>&1 | tail -100 || (echo "::warning::Build failed — flagging for Sami review" && exit 1)
else
echo "::warning::No build.ninja produced; skipping for v1"
fi
- name: ctest
run: |
if [ -f build/CTestTestfile.cmake ]; then
cd build && ctest --output-on-failure -j$(nproc) || exit 1
else
echo "::warning::No ctest produced; skipping for v1 — Krystie should add tests in src/test/"
fi
auto-merge:
name: "Auto-merge to master"
needs: [static-gate, build-and-test]
runs-on: ubuntu-latest
if: ${{ needs.static-gate.result == 'success' && needs.build-and-test.result == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.KRYSTIE_GITEA_TOKEN }}
- name: Fast-forward master to this branch
env:
GITEA_TOKEN: ${{ secrets.KRYSTIE_GITEA_TOKEN }}
BRANCH: ${{ github.ref_name }}
SHA: ${{ github.sha }}
run: |
set -euo pipefail
# The wip branch is master + N Krystie commits. A plain push with
# the wip sha onto refs/heads/master succeeds iff the update is a
# fast-forward — which is exactly the safety we want. (Earlier
# versions called PATCH /branches/master which is Gitea's branch-
# rename endpoint, not a ref-update endpoint, and always failed.)
REPO="${GITHUB_REPOSITORY}" # owner/name
GIT_URL="http://localhost:3030/${REPO}.git"
git -c "http.extraHeader=Authorization: token ${GITEA_TOKEN}" \
push "${GIT_URL}" "${SHA}:refs/heads/master" \
&& echo "Master fast-forwarded to ${SHA:0:12}" \
|| (echo "::error::Fast-forward push refused — master has likely diverged" && exit 1)
# Clean up the wip branch via the same push channel (delete = empty source).
git -c "http.extraHeader=Authorization: token ${GITEA_TOKEN}" \
push "${GIT_URL}" ":refs/heads/${BRANCH}" \
&& echo "Cleaned up wip branch ${BRANCH}" \
|| echo "::warning::Could not delete wip branch (it'll get pruned later)"
+65 -5
View File
@@ -2,7 +2,7 @@ name: Build All Platforms
on:
push:
branches: [master]
branches: [master, cpp20-modernization]
tags: ['v*']
pull_request:
branches: [master]
@@ -14,13 +14,15 @@ jobs:
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake ninja-build \
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
libevent-dev libminiupnpc-dev zlib1g-dev
librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev
- name: Configure
run: |
@@ -37,6 +39,52 @@ jobs:
- name: Run unit tests
run: cd build && ctest --output-on-failure || true
test-linux-sanitizers:
# ASan + UBSan build of the daemon + unit tests. Allowed to fail until
# findings are triaged — see .github/workflows/lint.yml comment block.
# Once the test suite is clean under sanitizers, drop continue-on-error.
runs-on: ubuntu-22.04
continue-on-error: true
env:
# ASan: leak detection off by default (BDB and OpenSSL produce noise on shutdown).
# Re-enable once we've quieted the legitimate suspects.
ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1:abort_on_error=1:print_stacktrace=1:strict_string_checks=1:detect_stack_use_after_return=1"
# UBSan: print full stack traces on first error and exit non-zero.
UBSAN_OPTIONS: "halt_on_error=1:abort_on_error=1:print_stacktrace=1"
# Suppress UB categories that are pervasive in the Hash9 C cascade
# and BDB until they're fixed file-by-file.
SAN_FLAGS: "-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=undefined -fno-sanitize=alignment,signed-integer-overflow,vptr"
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake ninja-build \
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev
- name: Configure with sanitizers
run: |
cmake -B build-san -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS="$SAN_FLAGS" \
-DCMAKE_CXX_FLAGS="$SAN_FLAGS" \
-DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \
-DCMAKE_SHARED_LINKER_FLAGS="$SAN_FLAGS" \
-DBUILD_QT=OFF \
-DBUILD_DAEMON=ON \
-DBUILD_TESTS=ON \
-DUSE_UPNP=OFF
- name: Build
run: cmake --build build-san -j$(nproc)
- name: Run unit tests under sanitizers
run: cd build-san && ctest --output-on-failure
build-windows-qt:
runs-on: windows-latest
defaults:
@@ -44,6 +92,8 @@ jobs:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: msys2/setup-msys2@v2
with:
@@ -61,6 +111,7 @@ jobs:
mingw-w64-x86_64-libevent
mingw-w64-x86_64-miniupnpc
mingw-w64-x86_64-zlib
mingw-w64-x86_64-rocksdb
- name: Set VERSION
run: |
@@ -194,6 +245,8 @@ jobs:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: msys2/setup-msys2@v2
with:
@@ -209,6 +262,7 @@ jobs:
mingw-w64-x86_64-libevent
mingw-w64-x86_64-miniupnpc
mingw-w64-x86_64-zlib
mingw-w64-x86_64-rocksdb
- name: Configure
run: |
@@ -256,6 +310,8 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set VERSION
run: |
@@ -274,7 +330,7 @@ jobs:
sudo apt-get install -y build-essential cmake ninja-build \
qtbase5-dev qttools5-dev-tools \
libboost-all-dev libssl-dev libdb++-dev \
libleveldb-dev libevent-dev libminiupnpc-dev zlib1g-dev
libleveldb-dev librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev
- name: Configure
run: |
@@ -373,6 +429,8 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set VERSION
run: |
@@ -390,7 +448,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y build-essential cmake ninja-build \
libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \
libevent-dev libminiupnpc-dev zlib1g-dev
librocksdb-dev libevent-dev libminiupnpc-dev zlib1g-dev
- name: Configure
run: |
@@ -503,6 +561,8 @@ jobs:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set VERSION
run: |
@@ -517,7 +577,7 @@ jobs:
- name: Install dependencies
run: |
brew install cmake ninja qt@5 openssl@3 boost berkeley-db@5 leveldb libevent miniupnpc
brew install cmake ninja qt@5 openssl@3 boost berkeley-db@5 leveldb rocksdb libevent miniupnpc
- name: Configure
run: |
+101
View File
@@ -0,0 +1,101 @@
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
submodules: recursive
- 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
+15
View File
@@ -1,3 +1,6 @@
# Per-user Claude Code settings (machine-specific paths/permissions)
.claude/
# Build artifacts
*.o
*.exe
@@ -7,8 +10,12 @@
*.a
/dist/
build/
build2/
build_*/
release/
debug/
build_err*.txt
*build_err.txt
/Makefile
Makefile.Debug
Makefile.Release
@@ -24,6 +31,7 @@ ui_*.h
qrc_*.cpp
*.pro.user
*.pro.user.*
*.qm
# Blockchain data
*.dat
@@ -63,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
-94
View File
@@ -1,94 +0,0 @@
# Triangles Codebase Cleanup Notes
## Overview
Systematic code quality improvements for the Triangles cryptocurrency codebase (v5.3.4+).
**Goal:** Improve maintainability without changing behavior or breaking consensus.
## Inventory
### TODOs/FIXMEs Found (38 total)
#### High Priority (Affects Safety/Correctness)
- `rpcmining.cpp:263` - **Thread safety issue** in mapNewBlock (static variable, no mutex)
- `walletmodel.cpp:249` - **Potential collision** in balance calculation
- `smessage.cpp:863, 2219, 2373` - **File size limit** (files must be split if >2GB)
#### Medium Priority (Encapsulation/Security)
- `protocol.h:50, 100, 132` - Public members should be private (3 locations)
- `wallet.h:378` - nOrderPos calculation should move elsewhere
- `wallet.cpp:733, 1732` - Change output handling needs improvement
- `rpcwallet.cpp:1474, 1513, 1569` - SecureString operator= missing (forced .c_str())
#### Low Priority (Nice-to-Have)
- `util.cpp:1322` - Disabled feature needs verification
- `tor/tor_embedded.cpp:209` - Tor 0.4.9+ shutdown API upgrade
- `init.cpp:442` - Remaining sanity checks (see Bitcoin issue #4081)
- `rpcmining.cpp:232` - DRM comment (unclear what it means)
- `smessage.cpp:*` - Various improvements (hash inclusion, thread safety, defaults)
- `qt/*` - UI improvements (decrypt not supported, message filtering, OSX startup)
#### External/Third-Party (Don't Touch)
- `leveldb/*` - LevelDB library TODOs (upstream issues)
## Code Quality Issues
### Using namespace std (37 files)
All in .cpp files - **this is fine for .cpp**, problematic only in headers.
No headers have this issue, so **no action needed**.
### Printf/Cout Usage (56 files)
Most cryptocurrency code uses printf for early init/error handling before logging is available.
**Review needed:** Check if these are legitimate early-init cases or should use LogPrintf.
## Cleanup Plan (Safest → Riskiest)
### Phase 1: Documentation & Comments ✅ SAFE
1. Document all TODOs with context (why deferred, what's needed)
2. Add function-level comments for complex logic
3. Improve inline comments for clarity
### Phase 2: Low-Risk Code Quality 🟨 MEDIUM RISK
4. Fix compiler warnings (-Wall -Wextra)
5. Add const correctness where missing
6. Remove commented-out dead code
7. Standardize code formatting (if inconsistent)
### Phase 3: Functional Improvements 🟥 HIGH RISK (Skip for now)
8. Fix thread safety issue in rpcmining.cpp (requires testing)
9. Improve protocol.h encapsulation (may affect other code)
10. Address >2GB file handling in smessage.cpp
## Decisions
### What NOT to Change
- **Consensus code** - main.cpp (validation), kernel.cpp (PoS), miner.cpp (staking)
- **Serialization** - Any READWRITE, serialize/deserialize code
- **Protocol constants** - Network message types, version numbers
- **Third-party code** - leveldb/, tor/, sph_types.h, xxhash/, lz4/
### What's Safe to Change
- Comments and documentation
- Variable names (in non-consensus code)
- Code organization (splitting large functions)
- Logging statements
- UI code (qt/)
- RPC interface (as long as API contract preserved)
## Initial Cleanup (2026-03-22)
### Actions Taken
1. Created this documentation file
2. Created cleanup/desloppify branch
3. Inventoried all TODOs/FIXMEs
### Next Steps
1. Add documentation comments to TODO items
2. Review printf/cout usage patterns
3. Check for compiler warnings
4. Consider low-risk improvements
## Notes
- This is a Bitcoin-derived codebase, so many patterns follow Bitcoin Core conventions
- Recent v5.3.x work already modernized to C++17 and removed Boost - good foundation
- Code is generally well-structured; main improvements are documentation and minor cleanup
-76
View File
@@ -1,76 +0,0 @@
# Triangles Cleanup Strategy - Safe Improvements
**Branch:** `cleanup/safe-improvements`
**Goal:** Improve code quality without touching consensus-critical code
## ✅ SAFE TO FIX
### 1. Compiler Warnings (Non-Consensus)
- **C++11 literal-suffix warnings** - Add spaces between literals and suffixes
- **Unused variables/functions** - Remove dead code (verify not consensus-critical first)
- **Deprecated-copy warnings** - Fix CScript assignment operator if safe
### 2. Code Style Improvements
- Remove `using namespace std` from headers (keep in .cpp files)
- Standardize logging patterns
- Improve code comments (remove unclear/misleading ones)
- Add context to TODOs/FIXMEs
### 3. Documentation
- Add inline comments for thread safety concerns
- Document collision vulnerabilities
- Improve function/class documentation
## ❌ DO NOT TOUCH
### Consensus-Critical Code
- **OpenSSL SHA256/RIPEMD160 usage** - Deprecated warnings OK, do not change
- **BN_is_prime_ex** - Crypto library deprecation, leave as-is
- **Hash algorithms** - Third-party libraries with warnings, consensus-critical
- **Block validation logic** - Any code affecting block/transaction validation
- **Merkle tree construction** - Core consensus
- **Proof-of-Work/Proof-of-Stake** - Staking/mining algorithms
### How to Identify Consensus Code
- Files in `src/` related to: `main.cpp`, `main.h`, block validation, transaction validation
- Anything in hash algorithm libraries
- Cryptographic primitives
- Network protocol message formats (version, serialization)
## Incremental Testing Strategy
1. **One warning category at a time**
2. **Compile after each change**
3. **Test basic functionality:**
- `trianglesd getinfo`
- `trianglesd getblockchaininfo`
- Verify block sync works
4. **Commit incrementally** with clear messages
## Warning Categories (From Build Output)
```
1. C++11 literal-suffix: ~20 instances (util.h, net.h, alert.cpp)
2. OpenSSL deprecation: SHA256, RIPEMD160 (DO NOT FIX)
3. BN_is_prime_ex: crypto library (DO NOT FIX)
4. Deprecated-copy: CScript assignment (REVIEW CAREFULLY)
5. Unused variables/functions: Various (SAFE IF NOT CONSENSUS)
```
## Branch History
- Previous work: `cleanup/desloppify` (documentation improvements, merged to master)
- This branch: Focus on safe compiler warnings and code quality
## Verification Checklist
Before pushing each commit:
- [ ] Code compiles successfully
- [ ] No new warnings introduced
- [ ] trianglesd runs without errors
- [ ] getinfo/getblockchaininfo work
- [ ] No consensus-critical code touched
---
**Principle:** When in doubt, don't touch it. A clean codebase is worthless if the blockchain forks.
+86 -3
View File
@@ -6,17 +6,37 @@ if(POLICY CMP0167)
endif()
project(Triangles
VERSION 5.7.8.0
VERSION 6.0.0
DESCRIPTION "Cryptographic Triangles Wallet"
LANGUAGES C CXX
)
# ── C++ Standard ──
set(CMAKE_CXX_STANDARD 17)
# C++20 required: RocksDB headers in MSYS2/Homebrew (8.x+) use `using enum`
# and defaulted operator== on user-defined types, both C++20-only.
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_C_STANDARD 11)
# ── Build acceleration ──
# ccache: auto-detect and use if available
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
message(STATUS "ccache found: ${CCACHE_PROGRAM}")
else()
message(STATUS "ccache not found — install it for faster rebuilds")
endif()
# Unity (jumbo) build: batch source files to reduce header parsing overhead
option(ENABLE_UNITY_BUILD "Enable CMake unity (jumbo) builds" OFF)
if(ENABLE_UNITY_BUILD)
set(CMAKE_UNITY_BUILD ON)
set(CMAKE_UNITY_BUILD_BATCH_SIZE 8)
endif()
# ── Output directories ──
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
@@ -53,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)
@@ -77,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)
@@ -113,4 +193,7 @@ message(STATUS " D-Bus: ${USE_DBUS}")
message(STATUS " ZMQ: ${USE_ZMQ}")
message(STATUS " Embedded Tor: ${USE_TOR_EMBEDDED}")
message(STATUS " Static linking: ${ENABLE_STATIC}")
message(STATUS " ccache: ${CCACHE_PROGRAM}")
message(STATUS " Unity build: ${ENABLE_UNITY_BUILD}")
message(STATUS " Precompiled header: ON")
message(STATUS "")
-220
View File
@@ -1,220 +0,0 @@
# Embedded Tor Integration Guide for Triangles
This guide explains how to compile Tor as a static library (`libtor.a`) and link
it directly into the Triangles wallet binary so that every node automatically
runs a Tor hidden service without needing an external Tor installation.
## Architecture Overview
```
trianglesd / triangles-qt
├── tor_embedded.cpp ← calls tor_run_main() in a background thread
├── tor_process.cpp ← fallback: launches external tor binary (already works)
├── onion_v3.cpp ← V3 onion address generation / SOCKS5 proxy logic
└── libtor.a ← aggregate static Tor library (built from official source)
```
When compiled with `ENABLE_TOR_EMBEDDED`, the wallet calls `tor_run_main()` from
`tor_api.h` on a dedicated thread. This gives the wallet a SOCKS5 proxy on
`127.0.0.1:19099` and a V3 hidden service on port 24112 (the P2P port).
When compiled **without** the flag, `tor_embedded.cpp` falls back to the external
`tor_process.cpp` which searches for and launches a system `tor` binary.
## Step 1: Add Tor as a Git Submodule
```bash
cd /path/to/triangles
git submodule add https://gitlab.torproject.org/tpo/core/tor.git src/tor/tor-src
cd src/tor/tor-src
git checkout release-0.4.9 # latest stable branch as of 2026
```
This puts the full Tor source at `src/tor/tor-src/`.
Current imported checkout in this repo: `release-0.4.9` at commit `1442ca4`.
There is also a helper build script at `src/tor/build-libtor.sh`.
## Step 2: Build libtor.a
Tor uses autotools. Build it as a static library:
```bash
cd src/tor/tor-src
# Install Tor build dependencies
sudo apt install autoconf automake libtool pkg-config \
libssl-dev libevent-dev zlib1g-dev
# Generate configure script
./autogen.sh
# Configure for static library build (disable unneeded modules)
./configure \
--enable-static-tor \
--disable-module-relay \
--disable-module-dirauth \
--disable-asciidoc \
--disable-manpage \
--disable-html-manual \
--disable-unittests \
--disable-tool-name-check \
--with-openssl-dir=/usr \
--with-libevent-dir=/usr \
--with-zlib-dir=/usr \
--prefix=/usr/local
make -j$(nproc)
```
Or from the repo root:
```bash
./src/tor/build-libtor.sh
```
After building, the static libraries are in `src/tor/tor-src/`:
- `libtor.a`
- `src/lib/libtor-*.a` (multiple component libs)
The header `src/feature/api/tor_api.h` provides the public C API:
```c
tor_main_configuration_t *tor_main_configuration_new(void);
int tor_main_configuration_set_command_line(tor_main_configuration_t *cfg,
int argc, char *argv[]);
int tor_run_main(const tor_main_configuration_t *);
void tor_main_configuration_free(tor_main_configuration_t *);
```
## Step 3: Build Triangles with Embedded Tor
### Linux (makefile.unix)
```bash
cd src
# Point to Tor's built libraries and headers
make -f makefile.unix \
USE_TOR_EMBEDDED=1
```
You may need to adjust the `-l` flags in the makefile depending on the exact
library names Tor produces. Check `src/tor/tor-src/` after building:
```bash
find tor/tor-src -name '*.a' | sort
```
On the imported `release-0.4.9` checkout in this repo, the simplest working
link path is the aggregate `libtor.a` plus the normal dependency libraries.
### Windows (triangles-qt.pro)
Add to `triangles-qt.pro`:
```qmake
qmake "USE_TOR_EMBEDDED=1" \
"TOR_SOURCE_ROOT=src/tor/tor-src"
```
Both build systems now default to:
- source root: `src/tor/tor-src`
- include path: `src/tor/tor-src/src/feature/api`
- library path: `src/tor/tor-src`
- embedded Tor library: `-ltor`
On Windows, the imported Tor `0.4.9.5` build also needed:
- `-llzma`
- `-lzstd`
- `-liphlpapi`
- `-lshlwapi` (already linked by Triangles)
## Step 4: Wire into init.cpp
The global hooks `StartEmbeddedTor()` and `StopEmbeddedTor()` need to be called
from `init.cpp`. Add these calls:
### In AppInit2() (after network init, before starting node):
```cpp
#include "tor/tor_embedded.h"
// Near the end of AppInit2, after network initialization:
if (!StartEmbeddedTor()) {
printf("WARNING: Embedded Tor failed to start. .onion connectivity unavailable.\n");
// Non-fatal: wallet works without Tor, just no .onion
}
```
### In Shutdown():
```cpp
StopEmbeddedTor();
```
## Step 5: Configure SOCKS Proxy for Outbound Connections
After Tor starts, the wallet needs to route `.onion` connections through the
SOCKS5 proxy. In `net.cpp`, after Tor is initialized:
```cpp
// If embedded Tor is running, use its SOCKS proxy for .onion addresses
CTorEmbedded* tor = CTorEmbedded::GetInstance();
if (tor->IsRunning()) {
// Set proxy for .onion connections
proxyType addrProxy(CService("127.0.0.1", tor->GetSocksPort()), 5);
SetNameProxy(addrProxy);
}
```
## Runtime Flags
The embedded Tor respects these command-line flags:
| Flag | Default | Description |
|------|---------|-------------|
| `-notor` | false | Disable Tor entirely |
| `-torsocks=PORT` | 19099 | SOCKS5 proxy port |
| `-torhsport=PORT` | 24112 | Hidden service virtual port |
## File Layout After Integration
```
src/tor/
├── tor-src/ ← git submodule (official Tor repo)
│ └── src/
│ ├── lib/libtor-*.a
│ └── feature/api/tor_api.h
│ └── libtor.a
├── tor_embedded.h ← CTorEmbedded class header
├── tor_embedded.cpp ← implementation (calls tor_run_main)
├── tor_process.h ← external Tor process manager (fallback)
├── tor_process.cpp
├── onion_v3.h ← V3 onion address utilities
├── onion_v3.cpp
├── anonymize.h ← data dir helpers
├── anonymize.cpp
└── LICENSE
```
## Reference: How VERGE (XVG) Does It
VERGE uses the same pattern. Their implementation is at:
- `src/torcontroller.cpp` (~100 lines)
- They use `tor_main()` (older API, pre-0.4.5)
- Git submodule at `src/tor/` pointing to `release-0.4.8` branch
- Build Tor as part of their `depends/` system
Key difference: modern Tor (0.4.5+) uses `tor_run_main()` with a configuration
object instead of raw `tor_main(int argc, char** argv)`.
## Troubleshooting
**Tor fails to bootstrap**: Check firewall rules. Tor needs outbound TCP to the
Tor network (ports 80, 443, 9001, 9030).
**Link errors with libtor**: Prefer the aggregate `libtor.a` from the top level
of the Tor build tree. On the imported Windows/MSYS2 build in this repo, the
minimal verified link set was:
```
-ltor -levent -lssl -lcrypto -lz -llzma -lzstd -lws2_32 -liphlpapi -lshlwapi
```
**OpenSSL version mismatch**: Both Tor and Triangles must link against the same
OpenSSL version (3.x). If Tor was built against a different OpenSSL, rebuild it
with the same `--with-openssl-dir`.
+53 -33
View File
@@ -1,4 +1,4 @@
# Cryptographic Triangles (TRI) - v5.1.5
# Cryptographic Triangles (TRI)
Triangles is a privacy-focused cryptocurrency featuring Proof-of-Stake consensus, Tor v3 onion routing, and built-in encrypted messaging. Originally launched in July 2014, the chain was revived in March 2026 after being frozen since December 2022.
@@ -16,7 +16,7 @@ Triangles is a privacy-focused cryptocurrency featuring Proof-of-Stake consensus
|----------|-------|
| Algorithm | Hash9 (PoW blocks 0-9000), PoS from block 9001 |
| Block Time | ~120 seconds |
| Max Supply | 222,222 TRI |
| Max Supply | 2,222,222 TRI |
| PoS Reward | 33% annual, coin-age based |
| P2P Port | 24112 |
| RPC Port | 19112 |
@@ -24,48 +24,60 @@ Triangles is a privacy-focused cryptocurrency featuring Proof-of-Stake consensus
## Network Status
The Triangles network is live with seed nodes operating on both clearnet and Tor:
**Clearnet Seeds:**
- `194.233.88.206:24112`
- `74.208.167.19:24112`
The Triangles network operates exclusively over Tor for privacy:
**Tor v3 Seeds:**
- `gxvrhv3qitnc6kobrhsrse46bmcfitnybapor3or3oczzuxn6hfzxyid.onion:24112`
- `futmtrvh6j34t7s6yjdxfia6iwuyfzwh4k5eqfof5kfhoqk3xmi3qoqd.onion:24112`
- `jbpfhe7zw3qm67wy3j2ayysp3mnrjobopthnko3b3sgahqtecblwqmid.onion:24112`
- `uddaxjbo3lh2zskg7w6gwln4ty5cel7q4c5jbx7fdtv6zf2j47gdlyad.onion:24112`
- `el5sirhhleecuctpeeprelzubpqmoqivvra3rzlwbjttinxa4fq3wnid.onion:24112`
- `sj5dhybnlp3v4y5niyc5unrnd6s43lyx5ibup7rolyosjbi2u2hsbvyd.onion:24112`
- `i3kr5meha7se4ns3wss3h7v46m6uksfzv4wrohdqxpj6n35wyo2bvlid.onion:24112`
**DNS Seeds:**
- `seed1.cryptographic-triangles.org`
- `seed2.cryptographic-triangles.org`
**HTTP Seed List:**
- `seeds.cryptographic-triangles.org/seeds.txt` - Dynamically updated list of active onion peers
## Building from Source
Triangles uses CMake. All platforms follow the same build pattern.
### Dependencies
| Dependency | Minimum Version |
|------------|----------------|
| CMake | 3.16+ |
| C++ compiler | C++17 support |
| OpenSSL | 3.x |
| Boost | 1.90+ |
| Berkeley DB | 5.3 (with C++ bindings) |
| libevent | 2.x |
| LevelDB | bundled |
### Linux (Ubuntu 24.04 / Debian 12+)
Install dependencies:
```bash
sudo apt-get install -y build-essential libboost-all-dev libssl-dev \
libdb5.3++-dev libevent-dev zlib1g-dev libminiupnpc-dev
sudo apt-get install -y build-essential cmake ninja-build \
libboost-all-dev libssl-dev libdb5.3++-dev libevent-dev \
zlib1g-dev libminiupnpc-dev
```
Build the daemon:
For the Qt wallet, also install:
```bash
cd src/leveldb && make libleveldb.a libmemenv.a && cd ..
make -j$(nproc) -f makefile.unix USE_UPNP=0
strip trianglesd
sudo apt-get install -y qtbase5-dev qt5-qmake libqrencode-dev
```
Run the unit test suite:
Build:
```bash
make -C src -f makefile.unix test
cmake -B build -G Ninja -DBUILD_QT=ON
cmake --build build
```
### Linux (AlmaLinux 9 / RHEL 9)
Install dependencies:
```bash
sudo dnf install -y gcc-c++ make boost-devel openssl-devel libevent-devel \
zlib-devel miniupnpc-devel
sudo dnf install -y gcc-c++ cmake ninja-build boost-devel openssl-devel \
libevent-devel zlib-devel miniupnpc-devel
```
BDB 5.3 C++ bindings must be built from source on RHEL-based systems (the `libdb-devel` package does not include C++ headers). Download BDB 5.3.28 from Oracle and build with `--enable-cxx`.
@@ -76,17 +88,27 @@ Then build as above.
Open an MSYS2 MinGW64 shell and install:
```bash
pacman -S mingw-w64-x86_64-boost mingw-w64-x86_64-openssl \
pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja \
mingw-w64-x86_64-boost mingw-w64-x86_64-openssl \
mingw-w64-x86_64-db mingw-w64-x86_64-miniupnpc \
mingw-w64-x86_64-qt5-base mingw-w64-x86_64-qrencode
mingw-w64-x86_64-qt5-base mingw-w64-x86_64-qrencode \
mingw-w64-x86_64-libevent
```
Build the Qt wallet:
Build:
```bash
qmake triangles-qt.pro
make -j$(nproc)
cmake -B build -G Ninja -DBUILD_QT=ON
cmake --build build
```
### Build Options
| Option | Default | Description |
|--------|---------|-------------|
| `BUILD_QT` | ON | Build the Qt GUI wallet |
| `BUILD_DAEMON` | ON | Build the headless daemon |
| `BUILD_TESTS` | OFF | Build unit tests |
## Running
### First Run
@@ -103,15 +125,13 @@ txindex=1
listen=1
server=1
daemon=1
addnode=194.233.88.206
addnode=74.208.167.19
externalip=<your-public-ip>
proxy=127.0.0.1:9050
EOF
trianglesd
```
The node will connect to seed nodes and sync the blockchain automatically.
The node will connect to seed nodes over Tor and sync the blockchain automatically.
### Existing Wallet Holders
@@ -155,7 +175,7 @@ Messages are encrypted end-to-end using AES and distributed through the peer net
### Tor Support
To connect through Tor, install the Tor daemon and add to your config:
Triangles is designed as a Tor-only network. Install the Tor daemon and configure your proxy:
```
# triangles.conf
proxy=127.0.0.1:9050
@@ -199,7 +219,7 @@ Then set `externalip=<your-onion-address>` in `triangles.conf`.
- **Block 9001+** - Proof-of-Stake only
- **Block 17,651** - V5 hard fork (removed Tor v2, disabled checkpoint master key)
- **December 8, 2022** - Chain frozen (all nodes offline)
- **March 11, 2026** - Chain revived with v5.0.0.0, staking resumed
- **March 11, 2026** - Chain revived, staking resumed
## Project Structure
-123
View File
@@ -1,123 +0,0 @@
# TODO/FIXME Documentation
Detailed context for each TODO/FIXME in the codebase.
## Critical (Needs Attention)
### src/rpcmining.cpp:263 - Thread Safety Issue
```cpp
static mapNewBlock_t mapNewBlock; // FIXME: thread safety
```
**Issue:** Static variable accessed by multiple RPC threads without mutex protection.
**Impact:** Potential race condition in getwork RPC (used for mining).
**Status:** Low priority - PoW mining ended at block 9000, this code path rarely used.
**Fix:** Add std::mutex and lock_guard if getwork usage increases.
### src/qt/walletmodel.cpp:249 - Collision Risk
```cpp
if((total + nFeeRequired) > nBalance) // FIXME: could cause collisions in the future
```
**Issue:** Balance check may have edge case causing transaction collisions.
**Context:** In createTransaction fee calculation loop.
**Status:** Needs investigation - unclear what "collisions" means here.
**Fix:** Review Bitcoin Core's current implementation of this logic.
### src/smessage.cpp - File Size Limits
```cpp
// Lines 863, 2219, 2373: "TODO files must be split if > 2GB"
```
**Issue:** Secure message storage files not split when exceeding 2GB.
**Impact:** May fail on 32-bit systems or with large message volumes.
**Status:** Low priority - unlikely to reach 2GB in practice.
**Fix:** Implement file rotation when approaching 2GB limit.
## Medium Priority (Encapsulation/API)
### src/protocol.h - Make Members Private
```cpp
// Lines 50, 100, 132: "TODO: make private (improves encapsulation)"
```
**Issue:** CAddress, CInv, CMessageHeader have public data members.
**Impact:** Poor encapsulation, harder to maintain invariants.
**Status:** Deferred - would require extensive refactoring.
**Fix:** Add getter/setter methods, make members private, update all call sites.
### src/wallet.h:378 - nOrderPos Calculation
```cpp
nOrderPos = -1; // TODO: calculate elsewhere
```
**Issue:** Transaction ordering position calculated in constructor.
**Impact:** Minor - works but not ideal separation of concerns.
**Status:** Deferred - no functional issue.
**Fix:** Move calculation to WalletDB when transaction is added.
### src/rpcwallet.cpp / src/qt/askpassphrasedialog.cpp - SecureString Conversion
**Issue:** Password-handling paths were converting through `.c_str()` because `SecureString`
did not have a convenient conversion helper from `std::string`.
**Impact:** Unnecessary C-string shims in sensitive code paths.
**Status:** Resolved.
**Fix:** Added `MakeSecureString(const std::string&)` in `src/allocators.h` and updated
the wallet RPC and passphrase dialog call sites to use it directly.
## Low Priority (Nice-to-Have)
### src/util.cpp:1322 - Disabled Feature
```cpp
// TODO: This is currently disabled because it needs to be verified to work
```
**Context:** File descriptor management code.
**Status:** Intentionally disabled pending verification.
**Fix:** Test thoroughly, then enable if needed.
### src/tor/tor_embedded.cpp:209 - Tor Shutdown API
```cpp
// TODO: Tor 0.4.9+ may add tor_api_shutdown(), use it when available
```
**Context:** Embedded Tor cleanup.
**Status:** Waiting for upstream Tor API.
**Fix:** Check Tor 0.4.9+ releases for new API, integrate when stable.
### src/init.cpp:442 - Sanity Checks
```cpp
// TODO: remaining sanity checks, see #4081
```
**Context:** Bitcoin Core issue #4081 - additional startup sanity checks.
**Status:** Deferred - core checks already in place.
**Fix:** Review Bitcoin Core's current sanity check implementation.
### src/rpcmining.cpp:232 - DRM Comment
```cpp
CDataStream(coinbase, SER_NETWORK, PROTOCOL_VERSION) >> pblock->vtx[0]; // FIXME - DRM!
```
**Issue:** Unclear what "DRM" means here - likely "Data Race Maybe"?
**Status:** Needs clarification from original author.
**Fix:** Investigate if there's an actual issue, otherwise remove comment.
## Deferred (External/Low Impact)
### LevelDB TODOs (src/leveldb/*)
**Status:** Upstream LevelDB issues - don't modify embedded library.
**Action:** None - track upstream LevelDB project.
### Qt TODOs (src/qt/*)
**Status:** UI improvements, not critical.
**Action:** Track as nice-to-have enhancements.
### Secure Message TODOs (src/smessage.cpp)
Multiple minor improvements suggested:
- Include hash in certain operations
- Improve thread shutdown
- Set default recv/recvAnon behavior
- Update outbox after PoW completes
**Status:** Non-critical enhancements.
**Action:** Consider for future encrypted messaging upgrades.
## Summary
**Critical:** 3 items (thread safety, balance collision, file limits)
**Medium:** 6 items (encapsulation, SecureString)
**Low:** 5 items (disabled features, upstream APIs)
**Deferred:** ~24 items (external libs, minor enhancements)
**Recommendation:** Focus on documenting critical items in code comments, defer fixes until specific issues arise.
+281
View File
@@ -0,0 +1,281 @@
# Triangles (TRI) RPC Command Reference
This document describes every RPC command available in the Triangles daemon (`trianglesd`) and Qt wallet. Connect via JSON-RPC on port **19112** (default). All commands can also be run from the Qt wallet's debug console.
Triangles is a Tor-only PoS cryptocurrency. PoW ended at block 9000; from block 9001 onward the chain is pure Proof-of-Stake with 33% annual interest (coin-age based). Block time is 2 minutes. Max supply is 2,222,222 TRI.
---
## Server Control
| Command | Parameters | Description |
|---------|-----------|-------------|
| `help` | `[command]` | List all commands, or get detailed help for a specific command. |
| `stop` | | Shut down the daemon. |
---
## Blockchain
| Command | Parameters | Description |
|---------|-----------|-------------|
| `getbestblockhash` | | Returns the hash of the tip of the best chain. |
| `getblockcount` | | Returns the current block height. |
| `getblockhash` | `<index>` | Returns the block hash at the given height. |
| `getblock` | `<hash> [txinfo]` | Returns block details for the given hash. Set `txinfo=true` for full transaction data. |
| `getblockbynumber` | `<number> [txinfo]` | Same as `getblock` but accepts a height instead of a hash. |
| `getblockheader` | `<hash> [verbose=true]` | Returns block header data. If verbose is false, returns hex-encoded header. |
| `getblockchaininfo` | | Returns chain state info: chain name, block height, best hash, difficulty, etc. |
| `getdifficulty` | | Returns current PoW and PoS difficulty values. |
| `gettxoutsetinfo` | | Returns statistics about the UTXO set (total txouts, size, etc.). |
| `getrawmempool` | | Returns all transaction IDs currently in the mempool. |
| `getcheckpoint` | | Returns info about the current synchronized checkpoint. |
| `getchaintips` | | Returns info about all known chain tips (forks). |
| `invalidateblock` | `<hash>` | Permanently marks a block as invalid and rewinds the chain past it. |
| `reconsiderblock` | `<hash>` | Removes the invalid mark from a previously invalidated block. |
| `recalculatesupply` | | Recalculates money supply by summing all UTXOs. Updates the stored value at the chain tip and persists to disk. Returns old/new supply and difference. |
| `settxfee` | `<amount>` | Sets the transaction fee per kB. Amount is rounded to nearest 0.01. |
| `estimatefee` | `<nblocks>` | Estimates the fee per kB needed for confirmation within `nblocks` blocks. |
---
## Address Index
These commands query the address index. The daemon must be running with `-addressindex=1`.
| Command | Parameters | Description |
|---------|-----------|-------------|
| `getaddressbalance` | `{"addresses":["addr",...]}` | Returns confirmed balance for the given address(es). |
| `getaddressutxos` | `{"addresses":["addr",...]}` | Returns all unspent outputs for the given address(es). |
| `getaddresstxids` | `{"addresses":["addr",...], "start":n, "end":n}` | Returns transaction IDs for the given address(es), optionally filtered by block range. |
---
## Mining & Staking
| Command | Parameters | Description |
|---------|-----------|-------------|
| `getmininginfo` | | Returns mining-related info: height, difficulty, network hashrate, etc. |
| `getstakinginfo` | | Returns staking-related info: whether staking is active, weight, expected time to stake, etc. |
| `getsubsidy` | `[nTarget]` | Returns the PoW subsidy value for the given target height (historical reference only since PoW ended at block 9000). |
---
## Network
| Command | Parameters | Description |
|---------|-----------|-------------|
| `getconnectioncount` | | Returns the number of peer connections. |
| `getpeerinfo` | | Returns detailed info about each connected peer (address, version, ping time, etc.). |
| `getnetworkinfo` | | Returns P2P network state: version, protocol, peer mix, connections, relay fee, etc. |
| `getseedlist` | | Returns the list of configured seed nodes. |
| `addnode` | `<node> <add\|remove\|onetry>` | Add or remove a node from the manual peer list, or try connecting once. For Tor nodes use the `.onion` address. |
| `disconnectnode` | `<node>` | Immediately disconnects from the specified peer. |
| `sendalert` | `<message> <privatekey> <minver> <maxver> <priority> <id> [cancelupto]` | Broadcasts a network alert (requires the alert master private key). |
---
## Wallet — General
| Command | Parameters | Description |
|---------|-----------|-------------|
| `getinfo` | | Returns general info: version, balance, stake, block height, connections, etc. |
| `getwalletinfo` | | Returns wallet-specific info: balance, unconfirmed, immature, txcount, keypoolsize, etc. |
| `getbalance` | `[account] [minconf=1]` | Returns total available balance (optionally for a specific account). |
| `checkwallet` | | Checks wallet database for consistency errors. |
| `repairwallet` | | Attempts to repair the wallet database. |
| `resendtx` | | Re-broadcasts all unconfirmed wallet transactions. |
---
## Wallet — Addresses & Accounts
| Command | Parameters | Description |
|---------|-----------|-------------|
| `getnewaddress` | `[account]` | Generates a new receiving address (optionally assigned to an account). |
| `getnewpubkey` | `[account]` | Returns a new public key for the wallet. |
| `getaccountaddress` | `<account>` | Returns the current receiving address for the given account. |
| `setaccount` | `<address> <account>` | Assigns an address to the given account label. |
| `getaccount` | `<address>` | Returns the account label for the given address. |
| `getaddressesbyaccount` | `<account>` | Returns all addresses assigned to the given account. |
| `listaddressgroupings` | | Returns addresses grouped by common ownership (based on transaction history). |
| `validateaddress` | `<address>` | Validates a Triangles address and returns info (ismine, account, pubkey, etc.). |
| `validatepubkey` | `<pubkey>` | Validates a Triangles public key. |
| `listaccounts` | `[minconf=1]` | Returns all account names and their balances. |
---
## Wallet — Sending
| Command | Parameters | Description |
|---------|-----------|-------------|
| `sendtoaddress` | `<address> <amount> [comment] [comment-to]` | Sends TRI to an address. Returns the transaction ID. |
| `sendfrom` | `<fromaccount> <address> <amount> [minconf=1] [comment] [comment-to]` | Sends TRI from a specific account. |
| `sendmany` | `<fromaccount> {"addr":amount,...} [minconf=1] [comment]` | Sends TRI to multiple addresses in a single transaction. |
| `move` | `<fromaccount> <toaccount> <amount> [minconf=1] [comment]` | Moves funds between accounts (internal bookkeeping only, no on-chain tx). |
---
## Wallet — Transaction History
| Command | Parameters | Description |
|---------|-----------|-------------|
| `listtransactions` | `[account] [count=10] [from=0]` | Returns the most recent transactions (optionally filtered by account). |
| `listsinceblock` | `[blockhash] [target-confirmations]` | Returns all transactions since the given block. |
| `gettransaction` | `<txid>` | Returns detailed info about a wallet transaction. |
| `getreceivedbyaddress` | `<address> [minconf=1]` | Returns total amount received by an address. |
| `getreceivedbyaccount` | `<account> [minconf=1]` | Returns total amount received by an account. |
| `listreceivedbyaddress` | `[minconf=1] [includeempty=false]` | Returns amounts received for each address. |
| `listreceivedbyaccount` | `[minconf=1] [includeempty=false]` | Returns amounts received for each account. |
---
## Wallet — Staking Control
| Command | Parameters | Description |
|---------|-----------|-------------|
| `reservebalance` | `[reserve] [amount]` | Show or set a reserve balance that will not be used for staking. `reserve` is true/false, `amount` is the TRI to reserve. |
---
## Wallet — Security
| Command | Parameters | Description |
|---------|-----------|-------------|
| `encryptwallet` | `<passphrase>` | Encrypts the wallet with the given passphrase. **This shuts down the daemon.** The wallet must be re-started and unlocked afterward. |
| `walletpassphrase` | `<passphrase> <timeout> [stakingonly]` | Unlocks the wallet for `timeout` seconds. Set `stakingonly=true` to allow staking but prevent sending. |
| `walletpassphrasechange` | `<oldpassphrase> <newpassphrase>` | Changes the wallet encryption passphrase. |
| `walletlock` | | Immediately locks the wallet (removes decryption key from memory). |
| `keypoolrefill` | `[new-size]` | Tops up the pre-generated key pool. |
| `makekeypair` | `[prefix]` | Generates a new public/private keypair (not added to wallet). |
---
## Wallet — Backup & Import
| Command | Parameters | Description |
|---------|-----------|-------------|
| `backupwallet` | `<destination>` | Copies `wallet.dat` to the given file path. |
| `dumpwallet` | `<filename>` | Exports all wallet private keys to a plaintext file. |
| `dumpprivkey` | `<address>` | Returns the private key (WIF format) for the given address. |
| `importwallet` | `<filename>` | Imports keys from a wallet dump file. |
| `importprivkey` | `<privkey> [label]` | Imports a single private key (WIF format) with optional label. |
---
## Wallet — Multisig
| Command | Parameters | Description |
|---------|-----------|-------------|
| `addmultisigaddress` | `<nrequired> ["key",...] [account]` | Creates an M-of-N multisig address. `nrequired` is the number of signatures needed. |
| `addredeemscript` | `<redeemScript> [account]` | Adds a P2SH redeem script to the wallet. |
---
## Wallet — Message Signing
| Command | Parameters | Description |
|---------|-----------|-------------|
| `signmessage` | `<address> <message>` | Signs a message with the private key of the given address. |
| `verifymessage` | `<address> <signature> <message>` | Verifies a signed message. Returns true/false. |
---
## Raw Transactions
| Command | Parameters | Description |
|---------|-----------|-------------|
| `listunspent` | `[minconf=1] [maxconf=9999999] ["addr",...]` | Returns unspent transaction outputs, optionally filtered by address and confirmation count. |
| `createrawtransaction` | `[{"txid":"id","vout":n},...] {"addr":amount,...}` | Creates an unsigned raw transaction from the given inputs and outputs. |
| `decoderawtransaction` | `<hex>` | Decodes a raw transaction hex string into a JSON object. |
| `decodescript` | `<hex>` | Decodes a hex-encoded script into human-readable form. |
| `signrawtransaction` | `<hex> [prevtxs] [privkeys] [sighashtype="ALL"]` | Signs a raw transaction. Can provide previous tx outputs and private keys for offline signing. |
| `sendrawtransaction` | `<hex>` | Broadcasts a signed raw transaction to the network. Returns the txid. |
| `getrawtransaction` | `<txid> [verbose=0]` | Returns raw transaction data. Set verbose=1 for decoded JSON output. |
---
## Secure Messaging (SMSG)
Triangles has a built-in encrypted peer-to-peer messaging system. Messages are stored in a DHT-like bucket system and relayed through the network.
| Command | Parameters | Description |
|---------|-----------|-------------|
| `smsgenable` | | Enables the secure messaging system. |
| `smsgdisable` | | Disables the secure messaging system. |
| `smsgoptions` | `[list\|set <optname> <value>]` | View or change secure messaging options. |
| `smsglocalkeys` | `[whitelist\|all\|wallet\|recv +/- <addr>\|anon +/- <addr>]` | Manage which local keys participate in secure messaging. |
| `smsgaddkey` | `<address> <pubkey>` | Adds someone's public key so you can send them encrypted messages. |
| `smsggetpubkey` | `<address>` | Retrieves the public key for an address (needed to send messages to it). |
| `smsgsend` | `<fromAddr> <toAddr> <message>` | Sends an encrypted message from one of your addresses to a recipient. |
| `smsgsendanon` | `<toAddr> <message>` | Sends an anonymous encrypted message (no sender address attached). |
| `smsginbox` | `[all\|unread\|clear]` | View received secure messages. Default shows unread. |
| `smsgoutbox` | `[all\|clear]` | View sent secure messages. |
| `smsgscanchain` | | Scans the blockchain for secure message public keys. |
| `smsgscanbuckets` | | Scans stored message buckets for messages addressed to your keys. |
| `smsgbuckets` | `[stats\|dump]` | View secure message bucket statistics or dump contents. |
| `smsgbroadcast` | `<fromAddr> <message>` | Broadcasts a message to all SMSG participants (not encrypted to a single recipient). |
---
## Quick Reference — Common Tasks
**Check node status:**
```
getinfo
getblockcount
getconnectioncount
getstakinginfo
```
**Check balance and transactions:**
```
getbalance
listtransactions
```
**Send coins:**
```
walletpassphrase "yourpassphrase" 60
sendtoaddress "TRIaddress" 100
walletlock
```
**Unlock for staking only:**
```
walletpassphrase "yourpassphrase" 999999999 true
```
**Add a peer manually (Tor .onion):**
```
addnode "abcdef1234567890.onion" "add"
```
**Export/import a private key:**
```
dumpprivkey "TRIaddress"
importprivkey "5KPrivKeyHere" "mylabel"
```
**Fix incorrect money supply display:**
```
recalculatesupply
```
**Full reindex (rebuild block index from raw data):**
```
trianglesd -reindex
```
---
## Connection Info
| Setting | Value |
|---------|-------|
| Default RPC port | 19112 |
| Default P2P port | 24112 |
| Config file (Windows) | `%APPDATA%\triangles\triangles.conf` |
| Config file (Linux) | `~/.triangles/triangles.conf` |
| Protocol version | 70205 |
| Network | Tor-only |
+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()
+51 -16
View File
@@ -1,6 +1,7 @@
# cmake/GenerateBuildInfoScript.cmake
# Called at build time by the custom target in GenerateBuildInfo.cmake.
# Replicates the logic of share/genbuild.sh.
# Reads the version from clientversion.h (single source of truth) and
# appends git commit info for non-release builds.
# Read existing build.h first line if it exists
set(OLD_LINE "")
@@ -11,31 +12,69 @@ if(EXISTS "${OUTPUT_FILE}")
endif()
endif()
# Try exact tag match first (release builds)
# ── Read version from clientversion.h ──
file(STRINGS "${SOURCE_DIR}/src/clientversion.h" _ver_lines)
foreach(_line ${_ver_lines})
if(_line MATCHES "^#define CLIENT_VERSION_MAJOR +([0-9]+)")
set(VER_MAJOR "${CMAKE_MATCH_1}")
elseif(_line MATCHES "^#define CLIENT_VERSION_MINOR +([0-9]+)")
set(VER_MINOR "${CMAKE_MATCH_1}")
elseif(_line MATCHES "^#define CLIENT_VERSION_REVISION +([0-9]+)")
set(VER_REVISION "${CMAKE_MATCH_1}")
elseif(_line MATCHES "^#define CLIENT_VERSION_BUILD +([0-9]+)")
set(VER_BUILD "${CMAKE_MATCH_1}")
endif()
endforeach()
set(BASE_VERSION "v${VER_MAJOR}.${VER_MINOR}.${VER_REVISION}.${VER_BUILD}")
# ── Get git commit info (suffix only, not the version number) ──
set(GIT_SUFFIX "")
# Get short commit hash
execute_process(
COMMAND git describe --tags --exact-match
COMMAND git rev-parse --short HEAD
WORKING_DIRECTORY "${SOURCE_DIR}"
OUTPUT_VARIABLE GIT_DESC
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE _result
)
# Fall back to tag + commit distance
if(NOT _result EQUAL 0)
if(_result EQUAL 0 AND GIT_HASH)
# Check if working directory is dirty
execute_process(
COMMAND git describe --tags --dirty
COMMAND git diff-index --quiet HEAD --
WORKING_DIRECTORY "${SOURCE_DIR}"
OUTPUT_VARIABLE GIT_DESC
RESULT_VARIABLE _dirty
)
# Check if HEAD is exactly on a tag matching our version
execute_process(
COMMAND git describe --tags --exact-match HEAD
WORKING_DIRECTORY "${SOURCE_DIR}"
OUTPUT_VARIABLE GIT_TAG
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE _result
RESULT_VARIABLE _tag_result
)
if(NOT _result EQUAL 0)
set(GIT_DESC "")
set(_on_release_tag FALSE)
if(_tag_result EQUAL 0 AND GIT_TAG STREQUAL "${BASE_VERSION}")
set(_on_release_tag TRUE)
endif()
# Only add git suffix for non-release builds (not on exact version tag, or dirty)
if(NOT _on_release_tag OR NOT _dirty EQUAL 0)
set(GIT_SUFFIX "-g${GIT_HASH}")
if(NOT _dirty EQUAL 0)
set(GIT_SUFFIX "${GIT_SUFFIX}-dirty")
endif()
endif()
endif()
set(FULL_VERSION "${BASE_VERSION}${GIT_SUFFIX}")
# Get commit timestamp
execute_process(
COMMAND git log -n 1 --format=%ci
@@ -46,11 +85,7 @@ execute_process(
)
# Build new content
if(GIT_DESC)
set(NEW_LINE "#define BUILD_DESC \"${GIT_DESC}\"")
else()
set(NEW_LINE "// No build information available")
endif()
set(NEW_LINE "#define BUILD_DESC \"${FULL_VERSION}\"")
# Only write if changed
if(NOT "${OLD_LINE}" STREQUAL "${NEW_LINE}")
+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"
+26
View File
@@ -0,0 +1,26 @@
# systemd drop-in for trianglesd: enable unlimited core dumps so that
# crashes can be diagnosed post-mortem with `coredumpctl gdb`.
#
# Installation:
# sudo mkdir -p /etc/systemd/system/trianglesd.service.d
# sudo cp contrib/systemd/coredump.conf /etc/systemd/system/trianglesd.service.d/
# sudo systemctl daemon-reload
# sudo systemctl restart trianglesd
#
# Verify it took effect:
# systemctl show trianglesd | grep -E 'LimitCORE|LimitNOFILE'
#
# When the next crash happens, retrieve the stack trace with:
# coredumpctl list trianglesd
# coredumpctl gdb # most recent core; then run `bt full` at the (gdb) prompt
#
# See contrib/debug/CRASHDUMPS.md for the full playbook.
[Service]
# Allow the kernel to write a full core dump on SIGSEGV/SIGABRT/SIGBUS/SIGFPE.
LimitCORE=infinity
# systemd-coredump compresses and stores cores under /var/lib/systemd/coredump/.
# Make sure the package is installed:
# apt install systemd-coredump # Debian/Ubuntu
# dnf install systemd-coredump # Fedora/RHEL
+7
View File
@@ -0,0 +1,7 @@
# Krystie runner log
This file records autonomous-runner activity. Each entry is a doc-only
edit produced by the demo worker; once OpenClaw is wired in this log
will be replaced by real work.
- [2026-04-29T06:57:30Z] triangles_v5#1 — Smoke-test the Krystie loop runner
+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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

+53 -3
View File
@@ -23,6 +23,8 @@ add_library(hash9_crypto STATIC
)
target_include_directories(hash9_crypto PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
set_target_properties(hash9_crypto PROPERTIES LINKER_LANGUAGE C)
# Hash9 C files have colliding static symbols (IV512, DECL_STATE, etc.) — skip unity
set_target_properties(hash9_crypto PROPERTIES UNITY_BUILD OFF)
# ═══════════════════════════════════════════════════════════════════════════════
# 2. JSON library (header-only nlohmann/json via json_compat.h shim)
@@ -36,13 +38,14 @@ 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
hdwallet.cpp
crypto_ecdh.cpp
crypto_ecdsa.cpp
db.cpp
irc.cpp
key.cpp
keystore.cpp
main.cpp
@@ -60,6 +63,8 @@ set(CORE_SOURCES
pbkdf2.cpp
scrypt.cpp
smessage.cpp
syncmanager.cpp
chaindb_migrate.cpp
tor_embed_hooks.cpp
rest.cpp
trianglesrpc.cpp
@@ -71,7 +76,11 @@ set(CORE_SOURCES
rpcrawtransaction.cpp
rpcsmessage.cpp
zmqpublishnotifier.cpp
txdb-base.cpp
txdb-factory.cpp
txdb-leveldb.cpp
utxosnapshot.cpp
snapshotnet.cpp
lz4/lz4.c
tor/onion_v3.cpp
tor/tor_process.cpp
@@ -93,6 +102,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
@@ -110,7 +123,6 @@ target_link_libraries(triangles_common PUBLIC
leveldb_bundled
OpenSSL::SSL
OpenSSL::Crypto
Boost::filesystem
Boost::program_options
Boost::thread
Boost::chrono
@@ -140,6 +152,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 "")
@@ -184,6 +207,31 @@ endif()
add_dependencies(triangles_common generate_build_info build_leveldb)
# ── Precompiled header (heavy STL + Boost + OpenSSL includes, C++ only) ──
target_precompile_headers(triangles_common PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:<string$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<vector$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<map$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<deque$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<algorithm$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<sstream$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<stdexcept$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<cstdint$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<cstring$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<memory$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<functional$<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>>"
"$<$<COMPILE_LANGUAGE:CXX>:<openssl/rand.h$<ANGLE-R>>"
"$<$<COMPILE_LANGUAGE:CXX>:<openssl/evp.h$<ANGLE-R>>"
)
# ═══════════════════════════════════════════════════════════════════════════════
# 4. Headless daemon (trianglesd)
# ═══════════════════════════════════════════════════════════════════════════════
@@ -195,6 +243,7 @@ if(BUILD_DAEMON)
)
# No QT_GUI define — daemon gets the #if !defined(QT_GUI) code paths
target_link_libraries(trianglesd PRIVATE triangles_common)
target_precompile_headers(trianglesd REUSE_FROM triangles_common)
if(WIN32)
set_target_properties(trianglesd PROPERTIES SUFFIX ".exe")
@@ -258,6 +307,7 @@ if(BUILD_QT)
qt/trianglesunits.cpp
qt/qvaluecombobox.cpp
qt/askpassphrasedialog.cpp
qt/hdseeddialog.cpp
qt/notificator.cpp
qt/qtipcserver.cpp
qt/rpcconsole.cpp
+16 -17
View File
@@ -4,6 +4,8 @@
#include "addrman.h"
#include <cmath>
using namespace std;
int CAddrInfo::GetTriedBucket(const std::vector<unsigned char> &nKey) const
@@ -79,15 +81,14 @@ double CAddrInfo::GetChance(int64_t nNow) const
CAddrInfo* CAddrMan::Find(const CNetAddr& addr, int *pnId)
{
std::map<CNetAddr, int>::iterator it = mapAddr.find(addr);
auto it = mapAddr.find(addr);
if (it == mapAddr.end())
return NULL;
return nullptr;
if (pnId)
*pnId = (*it).second;
std::map<int, CAddrInfo>::iterator it2 = mapInfo.find((*it).second);
if (it2 != mapInfo.end())
return &(*it2).second;
return NULL;
*pnId = it->second;
if (auto it2 = mapInfo.find(it->second); it2 != mapInfo.end())
return &it2->second;
return nullptr;
}
CAddrInfo* CAddrMan::Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId)
@@ -175,13 +176,13 @@ int CAddrMan::ShrinkNew(int nUBucket)
int n[4] = {GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size())};
int nI = 0;
int nOldest = -1;
for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++)
for (const auto& elem : vNew)
{
if (nI == n[0] || nI == n[1] || nI == n[2] || nI == n[3])
{
assert(nOldest == -1 || mapInfo.count(*it) == 1);
if (nOldest == -1 || mapInfo[*it].nTime < mapInfo[nOldest].nTime)
nOldest = *it;
assert(nOldest == -1 || mapInfo.count(elem) == 1);
if (nOldest == -1 || mapInfo[elem].nTime < mapInfo[nOldest].nTime)
nOldest = elem;
}
nI++;
}
@@ -438,10 +439,8 @@ int CAddrMan::Check_()
if (vRandom.size() != nTried + nNew) return -7;
for (std::map<int, CAddrInfo>::iterator it = mapInfo.begin(); it != mapInfo.end(); it++)
for (auto& [n, info] : mapInfo)
{
int n = (*it).first;
CAddrInfo &info = (*it).second;
if (info.fInTried)
{
@@ -465,10 +464,10 @@ int CAddrMan::Check_()
for (int n=0; n<vvTried.size(); n++)
{
std::vector<int> &vTried = vvTried[n];
for (std::vector<int>::iterator it = vTried.begin(); it != vTried.end(); it++)
for (const auto& elem : vTried)
{
if (!setTried.count(*it)) return -11;
setTried.erase(*it);
if (!setTried.count(elem)) return -11;
setTried.erase(elem);
}
}
-276
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
+39 -37
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;
@@ -66,7 +66,7 @@ public:
if(it == histogram.end()) // Newly locked page
{
locker.Lock(reinterpret_cast<void*>(page), page_size);
histogram.insert(std::make_pair(page, 1));
histogram.insert({page, 1});
}
else // Page was already locked; increase counter
{
@@ -78,7 +78,7 @@ public:
// For all pages in affected range, decrease lock count
void UnlockRange(void *p, size_t size)
{
boost::mutex::scoped_lock lock(mutex);
std::lock_guard<std::mutex> lock(mutex);
if(!size) return;
const size_t base_addr = reinterpret_cast<size_t>(p);
const size_t start_page = base_addr & page_mask;
@@ -101,13 +101,13 @@ public:
// Get number of locked pages for diagnostics
int GetLockedPageCount()
{
boost::mutex::scoped_lock lock(mutex);
std::lock_guard<std::mutex> lock(mutex);
return histogram.size();
}
private:
Locker locker;
boost::mutex mutex;
std::mutex mutex;
size_t page_size, page_mask;
// map of page base address to lock count
typedef std::map<size_t,int> Histogram;
@@ -182,35 +182,36 @@ private:
template<typename T>
struct secure_allocator : public std::allocator<T>
{
// MSVC8 default copy constructor is broken
// C++20 removed pointer/reference/etc. member typedefs from std::allocator
// and removed the 2-arg allocate(n, hint). Define what we still need
// directly instead of pulling from base.
typedef std::allocator<T> base;
typedef typename base::size_type size_type;
typedef typename base::difference_type difference_type;
typedef typename base::pointer pointer;
typedef typename base::const_pointer const_pointer;
typedef typename base::reference reference;
typedef typename base::const_reference const_reference;
typedef typename base::value_type value_type;
secure_allocator() throw() {}
secure_allocator(const secure_allocator& a) throw() : base(a) {}
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
secure_allocator() noexcept {}
secure_allocator(const secure_allocator& a) noexcept : base(a) {}
template <typename U>
secure_allocator(const secure_allocator<U>& a) throw() : base(a) {}
~secure_allocator() throw() {}
secure_allocator(const secure_allocator<U>& a) noexcept : base(a) {}
~secure_allocator() noexcept {}
template<typename _Other> struct rebind
{ typedef secure_allocator<_Other> other; };
T* allocate(std::size_t n, const void *hint = 0)
T* allocate(std::size_t n)
{
T *p;
p = std::allocator<T>::allocate(n, hint);
if (p != NULL)
T* p = std::allocator<T>::allocate(n);
if (p != nullptr)
LockedPageManager::instance.LockRange(p, sizeof(T) * n);
return p;
}
void deallocate(T* p, std::size_t n)
{
if (p != NULL)
if (p != nullptr)
{
memset(p, 0, sizeof(T) * n);
LockedPageManager::instance.UnlockRange(p, sizeof(T) * n);
@@ -226,33 +227,34 @@ struct secure_allocator : public std::allocator<T>
template<typename T>
struct zero_after_free_allocator : public std::allocator<T>
{
// MSVC8 default copy constructor is broken
// C++20 removed pointer/reference/etc. member typedefs from std::allocator.
// Define what we still need directly instead of pulling from base.
typedef std::allocator<T> base;
typedef typename base::size_type size_type;
typedef typename base::difference_type difference_type;
typedef typename base::pointer pointer;
typedef typename base::const_pointer const_pointer;
typedef typename base::reference reference;
typedef typename base::const_reference const_reference;
typedef typename base::value_type value_type;
zero_after_free_allocator() throw() {}
zero_after_free_allocator(const zero_after_free_allocator& a) throw() : base(a) {}
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
zero_after_free_allocator() noexcept {}
zero_after_free_allocator(const zero_after_free_allocator& a) noexcept : base(a) {}
template <typename U>
zero_after_free_allocator(const zero_after_free_allocator<U>& a) throw() : base(a) {}
~zero_after_free_allocator() throw() {}
zero_after_free_allocator(const zero_after_free_allocator<U>& a) noexcept : base(a) {}
~zero_after_free_allocator() noexcept {}
template<typename _Other> struct rebind
{ typedef zero_after_free_allocator<_Other> other; };
void deallocate(T* p, std::size_t n)
{
if (p != NULL)
if (p != nullptr)
memset(p, 0, sizeof(T) * n);
std::allocator<T>::deallocate(p, n);
}
};
// This is exactly like std::string, but with a custom allocator.
typedef std::basic_string<char, std::char_traits<char>, secure_allocator<char> > SecureString;
using SecureString = std::basic_string<char, std::char_traits<char>, secure_allocator<char>>;
static inline SecureString MakeSecureString(const std::string& value)
{
+23 -14
View File
@@ -11,7 +11,9 @@
#include "version.h"
#include <openssl/bn.h>
#include <openssl/opensslv.h>
#include <algorithm>
#include <stdexcept>
#include <vector>
@@ -36,20 +38,20 @@ public:
CAutoBN_CTX()
{
pctx = BN_CTX_new();
if (pctx == NULL)
if (pctx == nullptr)
throw bignum_error("CAutoBN_CTX : BN_CTX_new() returned NULL");
}
~CAutoBN_CTX()
{
if (pctx != NULL)
if (pctx != nullptr)
BN_CTX_free(pctx);
}
operator BN_CTX*() { return pctx; }
BN_CTX& operator*() { return *pctx; }
BN_CTX** operator&() { return &pctx; }
bool operator!() { return (pctx == NULL); }
bool operator!() { return (pctx == nullptr); }
};
@@ -63,14 +65,14 @@ public:
CBigNum()
{
pbn = BN_new();
if (pbn == NULL)
if (pbn == nullptr)
throw bignum_error("CBigNum::CBigNum() : BN_new() returned NULL");
}
CBigNum(const CBigNum& b)
{
pbn = BN_new();
if (pbn == NULL)
if (pbn == nullptr)
throw bignum_error("CBigNum::CBigNum(const CBigNum&) : BN_new() returned NULL");
if (!BN_copy(pbn, b.pbn))
{
@@ -88,7 +90,7 @@ public:
~CBigNum()
{
if (pbn != NULL)
if (pbn != nullptr)
BN_clear_free(pbn);
}
@@ -219,7 +221,7 @@ public:
uint64_t getuint64()
{
unsigned int nSize = BN_bn2mpi(pbn, NULL);
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
if (nSize < 4)
return 0;
std::vector<unsigned char> vch(nSize);
@@ -289,7 +291,7 @@ public:
uint256 getuint256() const
{
unsigned int nSize = BN_bn2mpi(pbn, NULL);
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
if (nSize < 4)
return 0;
std::vector<unsigned char> vch(nSize);
@@ -320,7 +322,7 @@ public:
std::vector<unsigned char> getvch() const
{
unsigned int nSize = BN_bn2mpi(pbn, NULL);
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
if (nSize <= 4)
return std::vector<unsigned char>();
std::vector<unsigned char> vch(nSize);
@@ -344,7 +346,7 @@ public:
unsigned int GetCompact() const
{
unsigned int nSize = BN_bn2mpi(pbn, NULL);
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
std::vector<unsigned char> vch(nSize);
nSize -= 4;
BN_bn2mpi(pbn, &vch[0]);
@@ -373,7 +375,7 @@ public:
psz++;
// hex string to bignum
static const signed char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 };
static constexpr signed char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 };
*this = 0;
while (isxdigit(*psz))
{
@@ -514,7 +516,7 @@ public:
*/
static CBigNum generatePrime(const unsigned int numBits, bool safe = false) {
CBigNum ret;
if(!BN_generate_prime_ex(ret.pbn, numBits, (safe == true), NULL, NULL, NULL))
if(!BN_generate_prime_ex(ret.pbn, numBits, (safe == true), nullptr, nullptr, nullptr))
throw bignum_error("CBigNum::generatePrime*= :BN_generate_prime_ex");
return ret;
}
@@ -540,7 +542,14 @@ public:
*/
bool isPrime(const int checks=BN_prime_checks) const {
CAutoBN_CTX pctx;
int ret = BN_is_prime_ex(pbn, checks, pctx, NULL);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
int ret = BN_is_prime_ex(pbn, checks, pctx, nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#pragma GCC diagnostic pop
#endif
if(ret < 0){
throw bignum_error("CBigNum::isPrime :BN_is_prime_ex");
}
@@ -705,7 +714,7 @@ inline const CBigNum operator/(const CBigNum& a, const CBigNum& b)
{
CAutoBN_CTX pctx;
CBigNum r;
if (!BN_div(r.pbn, NULL, a.pbn, b.pbn, pctx))
if (!BN_div(r.pbn, nullptr, a.pbn, b.pbn, pctx))
throw bignum_error("CBigNum::operator/ : BN_div failed");
return r;
}
+263
View File
@@ -0,0 +1,263 @@
// BIP39 English wordlist (2048 words, canonical). Auto-generated; do not edit.
#ifndef TRIANGLES_BIP39_ENGLISH_H
#define TRIANGLES_BIP39_ENGLISH_H
static const char* const BIP39_WORDLIST_EN[2048] = {
"abandon","ability","able","about","above","absent","absorb","abstract",
"absurd","abuse","access","accident","account","accuse","achieve","acid",
"acoustic","acquire","across","act","action","actor","actress","actual",
"adapt","add","addict","address","adjust","admit","adult","advance",
"advice","aerobic","affair","afford","afraid","again","age","agent",
"agree","ahead","aim","air","airport","aisle","alarm","album",
"alcohol","alert","alien","all","alley","allow","almost","alone",
"alpha","already","also","alter","always","amateur","amazing","among",
"amount","amused","analyst","anchor","ancient","anger","angle","angry",
"animal","ankle","announce","annual","another","answer","antenna","antique",
"anxiety","any","apart","apology","appear","apple","approve","april",
"arch","arctic","area","arena","argue","arm","armed","armor",
"army","around","arrange","arrest","arrive","arrow","art","artefact",
"artist","artwork","ask","aspect","assault","asset","assist","assume",
"asthma","athlete","atom","attack","attend","attitude","attract","auction",
"audit","august","aunt","author","auto","autumn","average","avocado",
"avoid","awake","aware","away","awesome","awful","awkward","axis",
"baby","bachelor","bacon","badge","bag","balance","balcony","ball",
"bamboo","banana","banner","bar","barely","bargain","barrel","base",
"basic","basket","battle","beach","bean","beauty","because","become",
"beef","before","begin","behave","behind","believe","below","belt",
"bench","benefit","best","betray","better","between","beyond","bicycle",
"bid","bike","bind","biology","bird","birth","bitter","black",
"blade","blame","blanket","blast","bleak","bless","blind","blood",
"blossom","blouse","blue","blur","blush","board","boat","body",
"boil","bomb","bone","bonus","book","boost","border","boring",
"borrow","boss","bottom","bounce","box","boy","bracket","brain",
"brand","brass","brave","bread","breeze","brick","bridge","brief",
"bright","bring","brisk","broccoli","broken","bronze","broom","brother",
"brown","brush","bubble","buddy","budget","buffalo","build","bulb",
"bulk","bullet","bundle","bunker","burden","burger","burst","bus",
"business","busy","butter","buyer","buzz","cabbage","cabin","cable",
"cactus","cage","cake","call","calm","camera","camp","can",
"canal","cancel","candy","cannon","canoe","canvas","canyon","capable",
"capital","captain","car","carbon","card","cargo","carpet","carry",
"cart","case","cash","casino","castle","casual","cat","catalog",
"catch","category","cattle","caught","cause","caution","cave","ceiling",
"celery","cement","census","century","cereal","certain","chair","chalk",
"champion","change","chaos","chapter","charge","chase","chat","cheap",
"check","cheese","chef","cherry","chest","chicken","chief","child",
"chimney","choice","choose","chronic","chuckle","chunk","churn","cigar",
"cinnamon","circle","citizen","city","civil","claim","clap","clarify",
"claw","clay","clean","clerk","clever","click","client","cliff",
"climb","clinic","clip","clock","clog","close","cloth","cloud",
"clown","club","clump","cluster","clutch","coach","coast","coconut",
"code","coffee","coil","coin","collect","color","column","combine",
"come","comfort","comic","common","company","concert","conduct","confirm",
"congress","connect","consider","control","convince","cook","cool","copper",
"copy","coral","core","corn","correct","cost","cotton","couch",
"country","couple","course","cousin","cover","coyote","crack","cradle",
"craft","cram","crane","crash","crater","crawl","crazy","cream",
"credit","creek","crew","cricket","crime","crisp","critic","crop",
"cross","crouch","crowd","crucial","cruel","cruise","crumble","crunch",
"crush","cry","crystal","cube","culture","cup","cupboard","curious",
"current","curtain","curve","cushion","custom","cute","cycle","dad",
"damage","damp","dance","danger","daring","dash","daughter","dawn",
"day","deal","debate","debris","decade","december","decide","decline",
"decorate","decrease","deer","defense","define","defy","degree","delay",
"deliver","demand","demise","denial","dentist","deny","depart","depend",
"deposit","depth","deputy","derive","describe","desert","design","desk",
"despair","destroy","detail","detect","develop","device","devote","diagram",
"dial","diamond","diary","dice","diesel","diet","differ","digital",
"dignity","dilemma","dinner","dinosaur","direct","dirt","disagree","discover",
"disease","dish","dismiss","disorder","display","distance","divert","divide",
"divorce","dizzy","doctor","document","dog","doll","dolphin","domain",
"donate","donkey","donor","door","dose","double","dove","draft",
"dragon","drama","drastic","draw","dream","dress","drift","drill",
"drink","drip","drive","drop","drum","dry","duck","dumb",
"dune","during","dust","dutch","duty","dwarf","dynamic","eager",
"eagle","early","earn","earth","easily","east","easy","echo",
"ecology","economy","edge","edit","educate","effort","egg","eight",
"either","elbow","elder","electric","elegant","element","elephant","elevator",
"elite","else","embark","embody","embrace","emerge","emotion","employ",
"empower","empty","enable","enact","end","endless","endorse","enemy",
"energy","enforce","engage","engine","enhance","enjoy","enlist","enough",
"enrich","enroll","ensure","enter","entire","entry","envelope","episode",
"equal","equip","era","erase","erode","erosion","error","erupt",
"escape","essay","essence","estate","eternal","ethics","evidence","evil",
"evoke","evolve","exact","example","excess","exchange","excite","exclude",
"excuse","execute","exercise","exhaust","exhibit","exile","exist","exit",
"exotic","expand","expect","expire","explain","expose","express","extend",
"extra","eye","eyebrow","fabric","face","faculty","fade","faint",
"faith","fall","false","fame","family","famous","fan","fancy",
"fantasy","farm","fashion","fat","fatal","father","fatigue","fault",
"favorite","feature","february","federal","fee","feed","feel","female",
"fence","festival","fetch","fever","few","fiber","fiction","field",
"figure","file","film","filter","final","find","fine","finger",
"finish","fire","firm","first","fiscal","fish","fit","fitness",
"fix","flag","flame","flash","flat","flavor","flee","flight",
"flip","float","flock","floor","flower","fluid","flush","fly",
"foam","focus","fog","foil","fold","follow","food","foot",
"force","forest","forget","fork","fortune","forum","forward","fossil",
"foster","found","fox","fragile","frame","frequent","fresh","friend",
"fringe","frog","front","frost","frown","frozen","fruit","fuel",
"fun","funny","furnace","fury","future","gadget","gain","galaxy",
"gallery","game","gap","garage","garbage","garden","garlic","garment",
"gas","gasp","gate","gather","gauge","gaze","general","genius",
"genre","gentle","genuine","gesture","ghost","giant","gift","giggle",
"ginger","giraffe","girl","give","glad","glance","glare","glass",
"glide","glimpse","globe","gloom","glory","glove","glow","glue",
"goat","goddess","gold","good","goose","gorilla","gospel","gossip",
"govern","gown","grab","grace","grain","grant","grape","grass",
"gravity","great","green","grid","grief","grit","grocery","group",
"grow","grunt","guard","guess","guide","guilt","guitar","gun",
"gym","habit","hair","half","hammer","hamster","hand","happy",
"harbor","hard","harsh","harvest","hat","have","hawk","hazard",
"head","health","heart","heavy","hedgehog","height","hello","helmet",
"help","hen","hero","hidden","high","hill","hint","hip",
"hire","history","hobby","hockey","hold","hole","holiday","hollow",
"home","honey","hood","hope","horn","horror","horse","hospital",
"host","hotel","hour","hover","hub","huge","human","humble",
"humor","hundred","hungry","hunt","hurdle","hurry","hurt","husband",
"hybrid","ice","icon","idea","identify","idle","ignore","ill",
"illegal","illness","image","imitate","immense","immune","impact","impose",
"improve","impulse","inch","include","income","increase","index","indicate",
"indoor","industry","infant","inflict","inform","inhale","inherit","initial",
"inject","injury","inmate","inner","innocent","input","inquiry","insane",
"insect","inside","inspire","install","intact","interest","into","invest",
"invite","involve","iron","island","isolate","issue","item","ivory",
"jacket","jaguar","jar","jazz","jealous","jeans","jelly","jewel",
"job","join","joke","journey","joy","judge","juice","jump",
"jungle","junior","junk","just","kangaroo","keen","keep","ketchup",
"key","kick","kid","kidney","kind","kingdom","kiss","kit",
"kitchen","kite","kitten","kiwi","knee","knife","knock","know",
"lab","label","labor","ladder","lady","lake","lamp","language",
"laptop","large","later","latin","laugh","laundry","lava","law",
"lawn","lawsuit","layer","lazy","leader","leaf","learn","leave",
"lecture","left","leg","legal","legend","leisure","lemon","lend",
"length","lens","leopard","lesson","letter","level","liar","liberty",
"library","license","life","lift","light","like","limb","limit",
"link","lion","liquid","list","little","live","lizard","load",
"loan","lobster","local","lock","logic","lonely","long","loop",
"lottery","loud","lounge","love","loyal","lucky","luggage","lumber",
"lunar","lunch","luxury","lyrics","machine","mad","magic","magnet",
"maid","mail","main","major","make","mammal","man","manage",
"mandate","mango","mansion","manual","maple","marble","march","margin",
"marine","market","marriage","mask","mass","master","match","material",
"math","matrix","matter","maximum","maze","meadow","mean","measure",
"meat","mechanic","medal","media","melody","melt","member","memory",
"mention","menu","mercy","merge","merit","merry","mesh","message",
"metal","method","middle","midnight","milk","million","mimic","mind",
"minimum","minor","minute","miracle","mirror","misery","miss","mistake",
"mix","mixed","mixture","mobile","model","modify","mom","moment",
"monitor","monkey","monster","month","moon","moral","more","morning",
"mosquito","mother","motion","motor","mountain","mouse","move","movie",
"much","muffin","mule","multiply","muscle","museum","mushroom","music",
"must","mutual","myself","mystery","myth","naive","name","napkin",
"narrow","nasty","nation","nature","near","neck","need","negative",
"neglect","neither","nephew","nerve","nest","net","network","neutral",
"never","news","next","nice","night","noble","noise","nominee",
"noodle","normal","north","nose","notable","note","nothing","notice",
"novel","now","nuclear","number","nurse","nut","oak","obey",
"object","oblige","obscure","observe","obtain","obvious","occur","ocean",
"october","odor","off","offer","office","often","oil","okay",
"old","olive","olympic","omit","once","one","onion","online",
"only","open","opera","opinion","oppose","option","orange","orbit",
"orchard","order","ordinary","organ","orient","original","orphan","ostrich",
"other","outdoor","outer","output","outside","oval","oven","over",
"own","owner","oxygen","oyster","ozone","pact","paddle","page",
"pair","palace","palm","panda","panel","panic","panther","paper",
"parade","parent","park","parrot","party","pass","patch","path",
"patient","patrol","pattern","pause","pave","payment","peace","peanut",
"pear","peasant","pelican","pen","penalty","pencil","people","pepper",
"perfect","permit","person","pet","phone","photo","phrase","physical",
"piano","picnic","picture","piece","pig","pigeon","pill","pilot",
"pink","pioneer","pipe","pistol","pitch","pizza","place","planet",
"plastic","plate","play","please","pledge","pluck","plug","plunge",
"poem","poet","point","polar","pole","police","pond","pony",
"pool","popular","portion","position","possible","post","potato","pottery",
"poverty","powder","power","practice","praise","predict","prefer","prepare",
"present","pretty","prevent","price","pride","primary","print","priority",
"prison","private","prize","problem","process","produce","profit","program",
"project","promote","proof","property","prosper","protect","proud","provide",
"public","pudding","pull","pulp","pulse","pumpkin","punch","pupil",
"puppy","purchase","purity","purpose","purse","push","put","puzzle",
"pyramid","quality","quantum","quarter","question","quick","quit","quiz",
"quote","rabbit","raccoon","race","rack","radar","radio","rail",
"rain","raise","rally","ramp","ranch","random","range","rapid",
"rare","rate","rather","raven","raw","razor","ready","real",
"reason","rebel","rebuild","recall","receive","recipe","record","recycle",
"reduce","reflect","reform","refuse","region","regret","regular","reject",
"relax","release","relief","rely","remain","remember","remind","remove",
"render","renew","rent","reopen","repair","repeat","replace","report",
"require","rescue","resemble","resist","resource","response","result","retire",
"retreat","return","reunion","reveal","review","reward","rhythm","rib",
"ribbon","rice","rich","ride","ridge","rifle","right","rigid",
"ring","riot","ripple","risk","ritual","rival","river","road",
"roast","robot","robust","rocket","romance","roof","rookie","room",
"rose","rotate","rough","round","route","royal","rubber","rude",
"rug","rule","run","runway","rural","sad","saddle","sadness",
"safe","sail","salad","salmon","salon","salt","salute","same",
"sample","sand","satisfy","satoshi","sauce","sausage","save","say",
"scale","scan","scare","scatter","scene","scheme","school","science",
"scissors","scorpion","scout","scrap","screen","script","scrub","sea",
"search","season","seat","second","secret","section","security","seed",
"seek","segment","select","sell","seminar","senior","sense","sentence",
"series","service","session","settle","setup","seven","shadow","shaft",
"shallow","share","shed","shell","sheriff","shield","shift","shine",
"ship","shiver","shock","shoe","shoot","shop","short","shoulder",
"shove","shrimp","shrug","shuffle","shy","sibling","sick","side",
"siege","sight","sign","silent","silk","silly","silver","similar",
"simple","since","sing","siren","sister","situate","six","size",
"skate","sketch","ski","skill","skin","skirt","skull","slab",
"slam","sleep","slender","slice","slide","slight","slim","slogan",
"slot","slow","slush","small","smart","smile","smoke","smooth",
"snack","snake","snap","sniff","snow","soap","soccer","social",
"sock","soda","soft","solar","soldier","solid","solution","solve",
"someone","song","soon","sorry","sort","soul","sound","soup",
"source","south","space","spare","spatial","spawn","speak","special",
"speed","spell","spend","sphere","spice","spider","spike","spin",
"spirit","split","spoil","sponsor","spoon","sport","spot","spray",
"spread","spring","spy","square","squeeze","squirrel","stable","stadium",
"staff","stage","stairs","stamp","stand","start","state","stay",
"steak","steel","stem","step","stereo","stick","still","sting",
"stock","stomach","stone","stool","story","stove","strategy","street",
"strike","strong","struggle","student","stuff","stumble","style","subject",
"submit","subway","success","such","sudden","suffer","sugar","suggest",
"suit","summer","sun","sunny","sunset","super","supply","supreme",
"sure","surface","surge","surprise","surround","survey","suspect","sustain",
"swallow","swamp","swap","swarm","swear","sweet","swift","swim",
"swing","switch","sword","symbol","symptom","syrup","system","table",
"tackle","tag","tail","talent","talk","tank","tape","target",
"task","taste","tattoo","taxi","teach","team","tell","ten",
"tenant","tennis","tent","term","test","text","thank","that",
"theme","then","theory","there","they","thing","this","thought",
"three","thrive","throw","thumb","thunder","ticket","tide","tiger",
"tilt","timber","time","tiny","tip","tired","tissue","title",
"toast","tobacco","today","toddler","toe","together","toilet","token",
"tomato","tomorrow","tone","tongue","tonight","tool","tooth","top",
"topic","topple","torch","tornado","tortoise","toss","total","tourist",
"toward","tower","town","toy","track","trade","traffic","tragic",
"train","transfer","trap","trash","travel","tray","treat","tree",
"trend","trial","tribe","trick","trigger","trim","trip","trophy",
"trouble","truck","true","truly","trumpet","trust","truth","try",
"tube","tuition","tumble","tuna","tunnel","turkey","turn","turtle",
"twelve","twenty","twice","twin","twist","two","type","typical",
"ugly","umbrella","unable","unaware","uncle","uncover","under","undo",
"unfair","unfold","unhappy","uniform","unique","unit","universe","unknown",
"unlock","until","unusual","unveil","update","upgrade","uphold","upon",
"upper","upset","urban","urge","usage","use","used","useful",
"useless","usual","utility","vacant","vacuum","vague","valid","valley",
"valve","van","vanish","vapor","various","vast","vault","vehicle",
"velvet","vendor","venture","venue","verb","verify","version","very",
"vessel","veteran","viable","vibrant","vicious","victory","video","view",
"village","vintage","violin","virtual","virus","visa","visit","visual",
"vital","vivid","vocal","voice","void","volcano","volume","vote",
"voyage","wage","wagon","wait","walk","wall","walnut","want",
"warfare","warm","warrior","wash","wasp","waste","water","wave",
"way","wealth","weapon","wear","weasel","weather","web","wedding",
"weekend","weird","welcome","west","wet","whale","what","wheat",
"wheel","when","where","whip","whisper","wide","width","wife",
"wild","will","win","window","wine","wing","wink","winner",
"winter","wire","wisdom","wise","wish","witness","wolf","woman",
"wonder","wood","wool","word","work","world","worry","worth",
"wrap","wreck","wrestle","wrist","write","wrong","yard","year",
"yellow","you","young","youth","zebra","zero","zone","zoo",
};
#endif
+376 -99
View File
@@ -2,16 +2,21 @@
// Distributed under the MIT/X11 software license
#include "bootstrap.h"
#include "utxosnapshot.h"
#include "txdb.h"
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/algorithm/string.hpp>
#include <filesystem>
#include <fstream>
#include <zlib.h>
#include "version.h"
#include "uint256.h"
#include "netbase.h"
#include "net.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <fstream>
#include <sstream>
@@ -19,12 +24,20 @@
#include <cstring>
#include <cstdlib>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#endif
// Forward declarations to avoid pulling in heavy consensus headers
extern bool fTestNet;
namespace Checkpoints { bool IsKnownCheckpoint(int nHeight, const uint256& hash); }
namespace fs = boost::filesystem;
using boost::asio::ip::tcp;
namespace fs = std::filesystem;
namespace Bootstrap {
@@ -33,63 +46,301 @@ bool NeedsBootstrap(const fs::path& dataDir)
return !fs::exists(dataDir / "blk0001.dat");
}
// Direct TCP connection bypassing Tor SOCKS proxy.
// Used for bootstrap downloads where the server is on clearnet.
static SOCKET ConnectDirectTCP(const std::string& host, int port, std::string& strError)
{
struct addrinfo hints, *result, *rp;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
std::string portStr = std::to_string(port);
int rc = getaddrinfo(host.c_str(), portStr.c_str(), &hints, &result);
if (rc != 0) {
strError = "DNS resolution failed for " + host;
return INVALID_SOCKET;
}
SOCKET hSocket = INVALID_SOCKET;
for (rp = result; rp != nullptr; rp = rp->ai_next) {
hSocket = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (hSocket == INVALID_SOCKET)
continue;
if (connect(hSocket, rp->ai_addr, (int)rp->ai_addrlen) == 0)
break; // success
closesocket(hSocket);
hSocket = INVALID_SOCKET;
}
freeaddrinfo(result);
if (hSocket == INVALID_SOCKET)
strError = "Cannot connect to " + host + ":" + portStr;
return hSocket;
}
// RAII wrapper for an HTTP(S) connection (socket + optional TLS)
struct HttpConn {
SOCKET sock;
SSL_CTX* ctx;
SSL* ssl;
HttpConn() : sock(INVALID_SOCKET), ctx(nullptr), ssl(nullptr) {}
~HttpConn() { Close(); }
void Close() {
if (ssl) { SSL_shutdown(ssl); SSL_free(ssl); ssl = nullptr; }
if (ctx) { SSL_CTX_free(ctx); ctx = nullptr; }
if (sock != INVALID_SOCKET) { closesocket(sock); sock = INVALID_SOCKET; }
}
bool Send(const char* data, size_t len) {
while (len > 0) {
int n = ssl ? SSL_write(ssl, data, (int)std::min(len, (size_t)65536))
: send(sock, data, (int)std::min(len, (size_t)65536), MSG_NOSIGNAL);
if (n <= 0) return false;
data += n;
len -= n;
}
return true;
}
int Recv(char* buf, int len) {
return ssl ? SSL_read(ssl, buf, len) : recv(sock, buf, len, 0);
}
// Read until delimiter found. Returns data including delimiter.
bool RecvUntil(std::string& out, const std::string& delim) {
out.clear();
char c;
while (true) {
int n = Recv(&c, 1);
if (n <= 0) return false;
out += c;
if (out.size() >= delim.size() &&
out.compare(out.size() - delim.size(), delim.size(), delim) == 0)
return true;
if (out.size() > 64 * 1024) return false; // header too large
}
}
// Establish TLS on an already-connected socket
bool StartTLS(const std::string& hostname, std::string& strError) {
ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
strError = "Failed to create SSL context";
return false;
}
// Skip cert verification — we verify data integrity via checkpoint hashes
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr);
ssl = SSL_new(ctx);
if (!ssl) {
strError = "Failed to create SSL object";
return false;
}
SSL_set_fd(ssl, (int)sock);
SSL_set_tlsext_host_name(ssl, hostname.c_str()); // SNI
if (SSL_connect(ssl) != 1) {
unsigned long err = ERR_get_error();
char errBuf[256];
ERR_error_string_n(err, errBuf, sizeof(errBuf));
strError = "TLS handshake failed with " + hostname + ": " + errBuf;
return false;
}
return true;
}
};
// Parse host, port, and path from an absolute URL.
// Sets useSSL, host, port, path. Returns false for unsupported schemes.
static bool ParseAbsoluteUrl(const std::string& url,
bool& useSSL, std::string& host,
int& port, std::string& path)
{
if (url.compare(0, 8, "https://") == 0) {
useSSL = true;
std::string rest = url.substr(8);
size_t pathStart = rest.find('/');
if (pathStart != std::string::npos) {
host = rest.substr(0, pathStart);
path = rest.substr(pathStart);
} else {
host = rest;
path = "/";
}
size_t colonPos = host.find(':');
if (colonPos != std::string::npos) {
port = std::atoi(host.c_str() + colonPos + 1);
host = host.substr(0, colonPos);
} else {
port = 443;
}
return true;
} else if (url.compare(0, 7, "http://") == 0) {
useSSL = false;
std::string rest = url.substr(7);
size_t pathStart = rest.find('/');
if (pathStart != std::string::npos) {
host = rest.substr(0, pathStart);
path = rest.substr(pathStart);
} else {
host = rest;
path = "/";
}
size_t colonPos = host.find(':');
if (colonPos != std::string::npos) {
port = std::atoi(host.c_str() + colonPos + 1);
host = host.substr(0, colonPos);
} else {
port = 80;
}
return true;
}
return false;
}
bool DownloadFile(const std::string& host, const std::string& urlPath,
const fs::path& destPath,
ProgressCallback progressFn,
std::string& strError)
std::string& strError,
bool noProxy,
int portOverride)
{
try {
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
std::string currentHost = host;
std::string currentPath = urlPath;
int currentPort = (portOverride > 0) ? portOverride : PORT;
bool useSSL = false;
std::string headerData;
int redirectCount = 0;
const int MAX_REDIRECTS = 5;
boost::system::error_code resolve_ec;
tcp::resolver::results_type endpoints =
resolver.resolve(host, std::to_string(PORT), resolve_ec);
if (resolve_ec) {
strError = "Cannot resolve host: " + host;
return false;
}
HttpConn conn;
tcp::socket socket(io_context);
boost::asio::connect(socket, endpoints);
// Connection + redirect loop
while (true) {
conn.Close(); // clean slate for each attempt
// Send HTTP GET request
std::string request =
"GET " + urlPath + " HTTP/1.1\r\n"
"Host: " + host + "\r\n"
"Connection: close\r\n"
"User-Agent: Triangles\r\n"
"\r\n";
boost::asio::write(socket, boost::asio::buffer(request));
// Read response headers
boost::asio::streambuf response_buf;
boost::asio::read_until(socket, response_buf, "\r\n\r\n");
std::istream response_stream(&response_buf);
// Parse status line
std::string http_version;
unsigned int status_code = 0;
response_stream >> http_version >> status_code;
std::string status_message;
std::getline(response_stream, status_message);
if (status_code != 200) {
strError = "HTTP error " + std::to_string(status_code) + " for " + urlPath;
return false;
}
// Parse headers for Content-Length
int64_t content_length = 0;
std::string header_line;
while (std::getline(response_stream, header_line) && header_line != "\r") {
std::string lower_header = header_line;
std::transform(lower_header.begin(), lower_header.end(),
lower_header.begin(), ::tolower);
if (lower_header.find("content-length:") == 0) {
content_length = std::stoll(header_line.substr(header_line.find(':') + 1));
if (noProxy) {
conn.sock = ConnectDirectTCP(currentHost, currentPort, strError);
if (conn.sock == INVALID_SOCKET)
return false;
} else {
CService addr;
if (!ConnectSocketByName(addr, conn.sock, currentHost.c_str(), currentPort, 30)) {
strError = "Cannot connect to " + currentHost + " (check Tor proxy)";
return false;
}
}
// Establish TLS when needed
if (useSSL) {
if (!conn.StartTLS(currentHost, strError))
return false;
printf("Bootstrap: TLS established with %s:%d\n",
currentHost.c_str(), currentPort);
}
// Send HTTP GET request
std::string request =
"GET " + currentPath + " HTTP/1.1\r\n"
"Host: " + currentHost + "\r\n"
"Connection: close\r\n"
"User-Agent: Triangles\r\n"
"\r\n";
if (!conn.Send(request.data(), request.size())) {
strError = "Failed to send request to " + currentHost;
return false;
}
// Read response headers
if (!conn.RecvUntil(headerData, "\r\n\r\n")) {
strError = "Failed to read HTTP headers from " + currentHost;
return false;
}
// Parse status code from "HTTP/1.x NNN ..."
unsigned int status_code = 0;
size_t sp = headerData.find(' ');
if (sp != std::string::npos)
status_code = atoi(headerData.c_str() + sp + 1);
// Handle HTTP redirects
if (status_code == 301 || status_code == 302 ||
status_code == 307 || status_code == 308) {
if (++redirectCount > MAX_REDIRECTS) {
strError = "Too many redirects for " + urlPath;
return false;
}
// Find Location header (case-insensitive)
std::string lowerHdr = headerData;
std::transform(lowerHdr.begin(), lowerHdr.end(),
lowerHdr.begin(), ::tolower);
size_t locPos = lowerHdr.find("\nlocation:");
if (locPos == std::string::npos) {
strError = "Redirect " + std::to_string(status_code) + " without Location header";
return false;
}
size_t valStart = locPos + 10; // skip "\nlocation:"
while (valStart < headerData.size() && headerData[valStart] == ' ')
valStart++;
size_t lineEnd = headerData.find("\r\n", valStart);
std::string location;
if (lineEnd != std::string::npos)
location = headerData.substr(valStart, lineEnd - valStart);
else
location = headerData.substr(valStart);
location = TrimString(location);
// Parse redirect URL — supports http://, https://, and relative paths
if (location.compare(0, 7, "http://") == 0 ||
location.compare(0, 8, "https://") == 0) {
if (!ParseAbsoluteUrl(location, useSSL, currentHost,
currentPort, currentPath)) {
strError = "Unsupported redirect location: " + location;
return false;
}
} else if (!location.empty() && location[0] == '/') {
currentPath = location;
} else {
strError = "Unsupported redirect location: " + location;
return false;
}
printf("Bootstrap: redirect %d -> %s%s%s (port %d)\n",
status_code, useSSL ? "https://" : "http://",
currentHost.c_str(), currentPath.c_str(), currentPort);
continue;
}
if (status_code != 200) {
strError = "HTTP error " + std::to_string(status_code) + " for " + currentPath;
return false;
}
break; // Got 200, proceed to download
}
// Parse Content-Length
int64_t content_length = 0;
std::string lowerHeaders = headerData;
std::transform(lowerHeaders.begin(), lowerHeaders.end(),
lowerHeaders.begin(), ::tolower);
size_t clPos = lowerHeaders.find("content-length:");
if (clPos != std::string::npos) {
size_t valStart = clPos + 15;
size_t lineEnd = lowerHeaders.find("\r\n", valStart);
if (lineEnd != std::string::npos)
content_length = std::stoll(headerData.substr(valStart, lineEnd - valStart));
}
// Open output file
@@ -99,46 +350,32 @@ bool DownloadFile(const std::string& host, const std::string& urlPath,
return false;
}
// Read body in chunks
int64_t bytes_written = 0;
// Write any data remaining in the header buffer (body starts here)
if (response_buf.size() > 0) {
std::istreambuf_iterator<char> eos;
std::string remaining(std::istreambuf_iterator<char>(response_stream), eos);
if (!remaining.empty()) {
fwrite(remaining.data(), 1, remaining.size(), file);
bytes_written += remaining.size();
}
}
// Read remaining body in chunks
std::vector<char> chunk(65536); // 64 KB
boost::system::error_code ec;
int64_t last_progress = 0;
char chunk[65536];
while (true) {
size_t n = socket.read_some(boost::asio::buffer(chunk), ec);
if (n > 0) {
fwrite(chunk.data(), 1, n, file);
bytes_written += n;
// Report progress every 256 KB
if (progressFn && (bytes_written - last_progress >= 262144)) {
last_progress = bytes_written;
progressFn(bytes_written, content_length);
}
}
if (ec == boost::asio::error::eof)
break;
if (ec) {
int n = conn.Recv(chunk, sizeof(chunk));
if (n < 0) {
fclose(file);
fs::remove(destPath);
strError = "Network error: " + ec.message();
strError = "Network error during download";
return false;
}
if (n == 0) break; // EOF
fwrite(chunk, 1, n, file);
bytes_written += n;
if (progressFn && (bytes_written - last_progress >= 262144)) {
last_progress = bytes_written;
progressFn(bytes_written, content_length);
}
}
fclose(file);
// conn destructor handles socket + SSL cleanup
// Verify download size if Content-Length was provided
if (content_length > 0 && bytes_written != content_length) {
@@ -158,13 +395,14 @@ bool DownloadFile(const std::string& host, const std::string& urlPath,
bool FetchFileList(const std::string& host,
std::vector<std::string>& files,
std::string& strError)
std::string& strError,
bool noProxy)
{
// Download filelist.txt to a temp file
fs::path tmpPath = fs::temp_directory_path() / "triangles_bootstrap_filelist.txt";
std::string urlPath = std::string(BASE_PATH) + "filelist.txt";
if (!DownloadFile(host, urlPath, tmpPath, nullptr, strError))
if (!DownloadFile(host, urlPath, tmpPath, nullptr, strError, noProxy))
return false;
// Read lines
@@ -177,7 +415,7 @@ bool FetchFileList(const std::string& host,
files.clear();
std::string line;
while (std::getline(in, line)) {
boost::trim(line);
line = TrimString(line);
if (!line.empty() && line[0] != '#')
files.push_back(line);
}
@@ -337,7 +575,7 @@ bool ParseManifest(const fs::path& manifestPath,
std::string line;
while (std::getline(in, line)) {
boost::trim(line);
line = TrimString(line);
if (line.empty() || line[0] == '#')
continue;
@@ -347,8 +585,8 @@ bool ParseManifest(const fs::path& manifestPath,
std::string key = line.substr(0, eq);
std::string val = line.substr(eq + 1);
boost::trim(key);
boost::trim(val);
key = TrimString(key);
val = TrimString(val);
if (key == "format")
manifest.format = std::atoi(val.c_str());
@@ -433,10 +671,14 @@ bool DownloadBootstrap(const std::string& host,
bool gotBlockFile = false;
// Try downloading bootstrap.tar.gz first
// Bootstrap server is on clearnet — bypass Tor proxy for DNS + HTTP
const bool noProxy = true;
fs::path tmpTarGz = dataDir / "bootstrap.tar.gz.tmp";
std::string tarUrl = std::string(BASE_PATH) + "triangles-bootstrap.tar.gz";
bool tarDownloaded = DownloadFile(host, tarUrl, tmpTarGz, progressFn, strError);
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);
@@ -451,7 +693,7 @@ bool DownloadBootstrap(const std::string& host,
// Fallback: try filelist.txt + individual file downloads
std::string fallbackError;
std::vector<std::string> files;
if (!FetchFileList(host, files, fallbackError)) {
if (!FetchFileList(host, files, fallbackError, noProxy)) {
if (!tarDownloaded)
strError = strError + " (fallback also failed: " + fallbackError + ")";
else
@@ -464,7 +706,7 @@ bool DownloadBootstrap(const std::string& host,
fs::create_directories(destPath.parent_path());
std::string urlPath = std::string(BASE_PATH) + files[i];
if (!DownloadFile(host, urlPath, destPath, progressFn, strError))
if (!DownloadFile(host, urlPath, destPath, progressFn, strError, noProxy))
return false;
}
@@ -476,16 +718,16 @@ bool DownloadBootstrap(const std::string& host,
return false;
}
// Check if the archive included a trusted pre-built index (txleveldb/)
// with a valid snapshot.manifest. If verified, keep it to skip the
// Check if the archive included a trusted pre-built index for the active
// backend with a valid snapshot.manifest. If verified, keep it to skip the
// multi-hour FastImportBlockFile() rebuild.
fs::path txleveldb = dataDir / "txleveldb";
fs::path chainDbPath = GetChainDataDir();
fs::path database = dataDir / "database";
fs::path manifestPath = dataDir / "snapshot.manifest";
bool keepIndex = false;
if (fs::exists(manifestPath) && fs::exists(txleveldb)) {
if (fs::exists(manifestPath) && fs::exists(chainDbPath)) {
SnapshotManifest manifest;
std::string manifestError;
@@ -512,9 +754,10 @@ bool DownloadBootstrap(const std::string& host,
if (!keepIndex) {
// No valid manifest or verification failed - delete the index.
// FastImportBlockFile() will rebuild from blk0001.dat on next startup.
printf("Bootstrap: removing extracted txleveldb/ (will rebuild index from blk0001.dat)\n");
if (fs::exists(txleveldb))
fs::remove_all(txleveldb);
printf("Bootstrap: removing extracted %s/ (will rebuild index from blk0001.dat)\n",
GetChainDataDir().filename().string().c_str());
if (fs::exists(chainDbPath))
fs::remove_all(chainDbPath);
}
// Always remove BDB database/ dir (wallet environment from another machine)
@@ -528,4 +771,38 @@ bool DownloadBootstrap(const std::string& host,
return true;
}
bool DownloadUtxoSnapshot(const std::string& host,
const fs::path& dataDir,
ProgressCallback progressFn,
std::string& strError)
{
const bool noProxy = true;
const char* snapshotFilename = "utxo-snapshot.bin";
// Download utxo-snapshot.bin to a temp file
fs::path tmpPath = dataDir / "utxo-snapshot.bin.tmp";
std::string urlPath = std::string(BASE_PATH) + snapshotFilename;
printf("Bootstrap: downloading UTXO snapshot from %s%s...\n", host.c_str(), urlPath.c_str());
if (!DownloadFile(host, urlPath, tmpPath, progressFn, strError, noProxy)) {
fs::remove(tmpPath);
return false;
}
printf("Bootstrap: UTXO snapshot downloaded, loading into database...\n");
// Load the snapshot into a fresh active chain DB
if (!UtxoSnapshot::LoadSnapshot(tmpPath, dataDir, strError)) {
fs::remove(tmpPath);
return false;
}
// Clean up the temp file
fs::remove(tmpPath);
printf("Bootstrap: UTXO snapshot loaded successfully.\n");
return true;
}
} // namespace Bootstrap
+22 -9
View File
@@ -7,13 +7,12 @@
#include <string>
#include <vector>
#include <functional>
#include <boost/filesystem.hpp>
#include <filesystem>
namespace Bootstrap {
// Bootstrap server configuration
static const char* DEFAULT_HOST = "bootstrap.cryptographic-triangles.org";
static const char* FALLBACK_HOST = "194.233.88.206";
static const char* BASE_PATH = "/";
static const int PORT = 80;
@@ -21,23 +20,29 @@ 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
// 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);
std::string& strError,
bool noProxy = false,
int portOverride = -1);
// Fetch the file manifest (list of relative paths to download)
bool FetchFileList(const std::string& host,
std::vector<std::string>& files,
std::string& strError);
std::string& strError,
bool noProxy = false);
// 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);
@@ -51,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);
@@ -59,6 +64,14 @@ namespace Bootstrap {
bool VerifyManifest(const SnapshotManifest& manifest,
std::string& strError);
// Download a UTXO snapshot and load it into a fresh txleveldb.
// 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 std::filesystem::path& dataDir,
ProgressCallback progressFn,
std::string& strError);
} // namespace Bootstrap
#endif // TRIANGLES_BOOTSTRAP_H
+198
View File
@@ -0,0 +1,198 @@
// Copyright (c) 2026 The Triangles developers.
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "chaindb_migrate.h"
#include "txdb-leveldb.h"
#include "txdb-rocksdb.h"
#include "util.h"
#include <filesystem>
#include <fstream>
#include <memory>
namespace fs = std::filesystem;
namespace {
struct ChainDbStats
{
int64_t nRecords = 0;
int64_t nUtxos = 0;
int64_t nUtxoValue = 0;
uint256 hashBestChain = 0;
int nDbFormat = 0;
};
bool CollectStats(CTxDBBase& db, ChainDbStats& stats, std::string& strError)
{
stats = ChainDbStats();
auto it = db.NewIterator();
for (it->Seek(std::string()); it->Valid(); it->Next())
stats.nRecords++;
int nUtxos = 0;
stats.nUtxoValue = db.SumUtxoValues(nUtxos);
stats.nUtxos = nUtxos;
db.ReadHashBestChain(stats.hashBestChain);
db.ReadDbFormat(stats.nDbFormat);
if (stats.nRecords <= 0) {
strError = "source chain database contains no records";
return false;
}
return true;
}
bool StatsMatch(const ChainDbStats& src, const ChainDbStats& dst, std::string& strError)
{
if (src.nRecords != dst.nRecords) {
strError = strprintf("record count mismatch after migration: source=%lld rocksdb=%lld",
(long long)src.nRecords, (long long)dst.nRecords);
return false;
}
if (src.nUtxos != dst.nUtxos || src.nUtxoValue != dst.nUtxoValue) {
strError = strprintf("UTXO mismatch after migration: source=(%lld,%lld) rocksdb=(%lld,%lld)",
(long long)src.nUtxos, (long long)src.nUtxoValue,
(long long)dst.nUtxos, (long long)dst.nUtxoValue);
return false;
}
if (src.hashBestChain != dst.hashBestChain) {
strError = strprintf("best-chain hash mismatch after migration: source=%s rocksdb=%s",
src.hashBestChain.ToString().c_str(),
dst.hashBestChain.ToString().c_str());
return false;
}
if (src.nDbFormat != dst.nDbFormat) {
strError = strprintf("dbformat mismatch after migration: source=%d rocksdb=%d",
src.nDbFormat, dst.nDbFormat);
return false;
}
return true;
}
} // namespace
bool MaybeMigrateLevelDbToRocksDb(bool fForce, std::string& strError)
{
strError.clear();
const fs::path dataDir = GetDataDir();
const fs::path levelPath = dataDir / "txleveldb";
const fs::path rocksPath = dataDir / "rocksdb";
const fs::path markerPath = rocksPath / "MIGRATION_INCOMPLETE";
if (!fs::exists(levelPath))
return true;
if (fs::exists(rocksPath)) {
if (fs::exists(markerPath)) {
printf("ChainDB migration: removing incomplete previous RocksDB migration\n");
fs::remove_all(rocksPath);
}
else if (!fForce)
return true;
else {
printf("ChainDB migration: removing existing RocksDB directory due to -migratechaindbforce\n");
fs::remove_all(rocksPath);
}
}
printf("ChainDB migration: copying LevelDB chain state to RocksDB...\n");
printf("ChainDB migration: source=%s destination=%s\n",
levelPath.string().c_str(), rocksPath.string().c_str());
try {
fs::create_directories(rocksPath);
{
std::ofstream marker(markerPath);
marker << "RocksDB migration in progress. Safe to delete this directory and retry.\n";
}
CTxDB source("r");
CRocksTxDB destination("c+");
ChainDbStats srcStats;
if (!CollectStats(source, srcStats, strError)) {
source.Close();
destination.Close();
return false;
}
if (!destination.TxnBegin()) {
strError = "failed to begin RocksDB migration batch";
source.Close();
destination.Close();
return false;
}
int64_t nCopied = 0;
auto it = source.NewIterator();
for (it->Seek(std::string()); it->Valid(); it->Next())
{
if (!destination.WriteRawRecordForMigration(it->KeyStr(), it->ValueStr())) {
destination.TxnAbort();
strError = "failed to write migrated record to RocksDB";
source.Close();
destination.Close();
return false;
}
if (++nCopied % 100000 == 0)
{
if (!destination.TxnCommit()) {
strError = "failed to commit RocksDB migration batch";
source.Close();
destination.Close();
return false;
}
printf("ChainDB migration: copied %lld / %lld records\n",
(long long)nCopied, (long long)srcStats.nRecords);
if (!destination.TxnBegin()) {
strError = "failed to begin RocksDB migration batch";
source.Close();
destination.Close();
return false;
}
}
}
if (!destination.TxnCommit()) {
strError = "failed to commit final RocksDB migration batch";
source.Close();
destination.Close();
return false;
}
ChainDbStats dstStats;
if (!CollectStats(destination, dstStats, strError)) {
source.Close();
destination.Close();
return false;
}
if (!StatsMatch(srcStats, dstStats, strError)) {
source.Close();
destination.Close();
return false;
}
printf("ChainDB migration: verified %lld records, %lld UTXOs, best=%s\n",
(long long)dstStats.nRecords,
(long long)dstStats.nUtxos,
dstStats.hashBestChain.ToString().substr(0,20).c_str());
source.Close();
destination.Close();
fs::remove(markerPath);
}
catch (std::exception& e) {
strError = e.what();
return false;
}
printf("ChainDB migration: complete. Legacy LevelDB was left untouched at %s\n",
levelPath.string().c_str());
return true;
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright (c) 2026 The Triangles developers.
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef TRIANGLES_CHAINDB_MIGRATE_H
#define TRIANGLES_CHAINDB_MIGRATE_H
#include <string>
// Migrate legacy LevelDB chain state from <datadir>/txleveldb to RocksDB in
// <datadir>/rocksdb. The source is never modified. Returns true when migration
// succeeds or when there is nothing to migrate.
bool MaybeMigrateLevelDbToRocksDb(bool fForce, std::string& strError);
#endif // TRIANGLES_CHAINDB_MIGRATE_H
+46 -12
View File
@@ -32,7 +32,26 @@ namespace Checkpoints
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
// Recent finality pin (PoS era). Closes the long unchecked span from
// 17650 to the live tip so stale-bootstrap / low-trust forks below
// this height are rejected outright. Hash from the canonical chain.
{ 2205000, uint256("0x6bdd3c5e5a32e1dd9a70e705f1a28d1dd84929f89579bd2696d41bc87f39446f")},
{ 2206004, uint256("0xb34e8e6a7bb7f52167d81aaad4d26f87a876898fdd0fce860916fc1aaf9a2a46")},
};
// Published UTXO snapshot file SHA256, keyed by snapshot height.
// Each entry binds height -> SHA256 of the canonical snapshot file produced by
// UtxoSnapshot::DumpSnapshot at that height. Used by SnapshotNet to verify
// P2P-delivered snapshots without trusting any peer.
//
// Maintainers: after producing a snapshot, sha256 the file and add an entry
// here. The corresponding (height, blockhash) must already exist in
// mapCheckpoints / mapCheckpointsTestnet.
static std::map<int, uint256> mapSnapshotHashes = {
{ 2206004, uint256("0x1419282dae817315ee1b955543f6248233fe5800f5e8488734a0ece5bd6781ea")},
};
static std::map<int, uint256> mapSnapshotHashesTestnet = {
};
static MapCheckpoints mapCheckpointsTestnet = {
@@ -48,7 +67,6 @@ namespace Checkpoints
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
};
bool CheckHardened(int nHeight, const uint256& hash)
@@ -75,6 +93,22 @@ namespace Checkpoints
return checkpoints.rbegin()->first;
}
int GetBestSnapshotHeight()
{
std::map<int, uint256>& snaps = (fTestNet ? mapSnapshotHashesTestnet : mapSnapshotHashes);
if (snaps.empty()) return 0;
return snaps.rbegin()->first;
}
bool GetSnapshotHash(int nHeight, uint256& fileHashOut)
{
std::map<int, uint256>& snaps = (fTestNet ? mapSnapshotHashesTestnet : mapSnapshotHashes);
auto it = snaps.find(nHeight);
if (it == snaps.end()) return false;
fileHashOut = it->second;
return true;
}
CBlockIndex* GetLastCheckpoint(const std::map<uint256, CBlockIndex*>& mapBlockIndex)
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
@@ -86,7 +120,7 @@ namespace Checkpoints
if (t != mapBlockIndex.end())
return t->second;
}
return NULL;
return nullptr;
}
// triangles: synchronized checkpoint (centrally broadcasted)
@@ -105,7 +139,7 @@ namespace Checkpoints
error("GetSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str());
else
return mapBlockIndex[hashSyncCheckpoint];
return NULL;
return nullptr;
}
// triangles: only descendant of current sync-checkpoint is allowed
@@ -153,7 +187,7 @@ namespace Checkpoints
bool WriteSyncCheckpoint(const uint256& hashCheckpoint)
{
CTxDB txdb;
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
txdb.TxnBegin();
if (!txdb.WriteSyncCheckpoint(hashCheckpoint))
{
@@ -179,7 +213,7 @@ namespace Checkpoints
return false;
}
CTxDB txdb;
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashPendingCheckpoint];
if (!pindexCheckpoint->IsInMainChain())
{
@@ -235,8 +269,8 @@ namespace Checkpoints
return false;
if (hashBlock == hashPendingCheckpoint)
return true;
if (mapOrphanBlocks.count(hashPendingCheckpoint)
&& hashBlock == WantedByOrphan(mapOrphanBlocks[hashPendingCheckpoint]))
if (mapOrphanBlocks.count(hashPendingCheckpoint)
&& hashBlock == WantedByOrphan(mapOrphanBlocks[hashPendingCheckpoint].get()))
return true;
return false;
}
@@ -250,7 +284,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());
@@ -325,7 +359,7 @@ namespace Checkpoints
if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig))
return error("SendSyncCheckpoint: Unable to sign checkpoint, check private key?");
if(!checkpoint.ProcessSyncCheckpoint(NULL))
if(!checkpoint.ProcessSyncCheckpoint(nullptr))
{
printf("WARNING: SendSyncCheckpoint: Failed to process checkpoint.\n");
return false;
@@ -386,7 +420,7 @@ bool CSyncCheckpoint::ProcessSyncCheckpoint(CNode* pfrom)
pfrom->PushGetBlocks(pindexBest, hashCheckpoint);
// ask directly as well in case rejected earlier by duplicate
// proof-of-stake because getblocks may not get it this time
pfrom->AskFor(CInv(MSG_BLOCK, mapOrphanBlocks.count(hashCheckpoint)? WantedByOrphan(mapOrphanBlocks[hashCheckpoint]) : hashCheckpoint));
pfrom->AskFor(CInv(MSG_BLOCK, mapOrphanBlocks.count(hashCheckpoint)? WantedByOrphan(mapOrphanBlocks[hashCheckpoint].get()) : hashCheckpoint));
}
return false;
}
@@ -394,7 +428,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())
{
+5
View File
@@ -45,6 +45,11 @@ namespace Checkpoints
// Return conservative estimate of total number of blocks, 0 if unknown
int GetTotalBlocksEstimate();
// Return the highest checkpoint height that has a published UTXO snapshot
// hash, along with the snapshot's file SHA256. Returns 0 height if none.
int GetBestSnapshotHeight();
bool GetSnapshotHash(int nHeight, uint256& fileHashOut);
// Returns last CBlockIndex* in mapBlockIndex that is a checkpoint
CBlockIndex* GetLastCheckpoint(const std::map<uint256, CBlockIndex*>& mapBlockIndex);
+12 -12
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++;
@@ -72,10 +72,10 @@ private:
nIdle--;
lock.unlock();
for (unsigned int i = 0; i < vChecks.size(); i++)
for (auto& check : vChecks)
{
if (fOk)
fOk = vChecks[i]();
fOk = check();
}
vChecks.clear();
@@ -102,7 +102,7 @@ public:
void StartBatch()
{
boost::unique_lock<boost::mutex> lock(mutex);
std::unique_lock<std::mutex> lock(mutex);
fAllOk = true;
nTodo = 0;
}
@@ -112,7 +112,7 @@ public:
if (vChecks.empty())
return;
boost::unique_lock<boost::mutex> lock(mutex);
std::unique_lock<std::mutex> lock(mutex);
for (typename std::vector<T>::iterator it = vChecks.begin(); it != vChecks.end(); ++it)
{
queue.push_back(T());
@@ -132,7 +132,7 @@ public:
void Quit()
{
boost::unique_lock<boost::mutex> lock(mutex);
std::unique_lock<std::mutex> lock(mutex);
fQuit = true;
condWorker.notify_all();
condMaster.notify_all();
+2 -2
View File
@@ -7,8 +7,8 @@
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 5
#define CLIENT_VERSION_MINOR 7
#define CLIENT_VERSION_REVISION 8
#define CLIENT_VERSION_MINOR 9
#define CLIENT_VERSION_REVISION 17
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
+2 -2
View File
@@ -75,7 +75,7 @@ bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned
bool fOk = true;
if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, chKey, chIV);
if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, chKey, chIV);
if (fOk) fOk = EVP_EncryptUpdate(ctx, &vchCiphertext[0], &nCLen, &vchPlaintext[0], nLen);
if (fOk) fOk = EVP_EncryptFinal_ex(ctx, (&vchCiphertext[0])+nCLen, &nFLen);
EVP_CIPHER_CTX_free(ctx);
@@ -102,7 +102,7 @@ bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingM
bool fOk = true;
if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, chKey, chIV);
if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, chKey, chIV);
if (fOk) fOk = EVP_DecryptUpdate(ctx, &vchPlaintext[0], &nPLen, &vchCiphertext[0], nLen);
if (fOk) fOk = EVP_DecryptFinal_ex(ctx, (&vchPlaintext[0])+nPLen, &nFLen);
EVP_CIPHER_CTX_free(ctx);
+3 -1
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;
@@ -78,7 +80,7 @@ public:
};
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;
using CKeyingMaterial = std::vector<unsigned char, secure_allocator<unsigned char>>;
/** Encryption/decryption context with key information */
class CCrypter
+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 nullptr.
int hash_xonly(unsigned char* output,
const unsigned char* x32,
const unsigned char* /*y32*/,
void* /*data*/)
{
std::memcpy(output, x32, 32);
return 1;
}
} // namespace
bool ECDH_xonly_secp256k1(unsigned char out32[32],
const unsigned char privkey32[32],
const unsigned char* pubkey,
std::size_t pubkey_len)
{
if (pubkey_len != 33 && pubkey_len != 65) return false;
secp256k1_context* ctx = GetECDHContext();
if (!ctx) return false;
secp256k1_pubkey pk;
if (!secp256k1_ec_pubkey_parse(ctx, &pk, pubkey, pubkey_len))
return false;
return secp256k1_ecdh(ctx, out32, &pk, privkey32, hash_xonly, nullptr) == 1;
}
+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
+22 -23
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;
@@ -134,7 +133,7 @@ void CDBEnv::MakeMock()
#ifdef DB_LOG_IN_MEMORY
dbenv.log_set_config(DB_LOG_IN_MEMORY, 1);
#endif
int ret = dbenv.open(NULL,
int ret = dbenv.open(nullptr,
DB_CREATE |
DB_INIT_LOCK |
DB_INIT_LOG |
@@ -156,10 +155,10 @@ CDBEnv::VerifyResult CDBEnv::Verify(std::string strFile, bool (*recoverFunc)(CDB
assert(mapFileUseCount.count(strFile) == 0);
Db db(&dbenv, 0);
int result = db.verify(strFile.c_str(), NULL, NULL, 0);
int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
if (result == 0)
return VERIFY_OK;
else if (recoverFunc == NULL)
else if (recoverFunc == nullptr)
return RECOVER_FAIL;
// Try to recover:
@@ -179,7 +178,7 @@ bool CDBEnv::Salvage(std::string strFile, bool fAggressive,
stringstream strDump;
Db db(&dbenv, 0);
int result = db.verify(strFile.c_str(), NULL, &strDump, flags);
int result = db.verify(strFile.c_str(), nullptr, &strDump, flags);
if (result == DB_VERIFY_BAD)
{
printf("Error: Salvage found errors, all data may not be recoverable.\n");
@@ -232,10 +231,10 @@ void CDBEnv::CheckpointLSN(std::string strFile)
CDB::CDB(const char *pszFile, const char* pszMode) :
pdb(NULL), activeTxn(NULL)
pdb(nullptr), activeTxn(nullptr)
{
int ret;
if (pszFile == NULL)
if (pszFile == nullptr)
return;
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
@@ -252,7 +251,7 @@ CDB::CDB(const char *pszFile, const char* pszMode) :
strFile = pszFile;
++bitdb.mapFileUseCount[strFile];
pdb = bitdb.mapDb[strFile];
if (pdb == NULL)
if (pdb == nullptr)
{
pdb = new Db(&bitdb.dbenv, 0);
@@ -265,8 +264,8 @@ CDB::CDB(const char *pszFile, const char* pszMode) :
throw runtime_error(strprintf("CDB() : failed to configure for no temp file backing for database %s", pszFile));
}
ret = pdb->open(NULL, // Txn pointer
fMockDb ? NULL : pszFile, // Filename
ret = pdb->open(nullptr, // Txn pointer
fMockDb ? nullptr : pszFile, // Filename
"main", // Logical db name
DB_BTREE, // Database type
nFlags, // Flags
@@ -275,7 +274,7 @@ CDB::CDB(const char *pszFile, const char* pszMode) :
if (ret != 0)
{
delete pdb;
pdb = NULL;
pdb = nullptr;
--bitdb.mapFileUseCount[strFile];
strFile = "";
throw runtime_error(strprintf("CDB() : can't open database file %s, error %d", pszFile, ret));
@@ -308,8 +307,8 @@ void CDB::Close()
return;
if (activeTxn)
activeTxn->abort();
activeTxn = NULL;
pdb = NULL;
activeTxn = nullptr;
pdb = nullptr;
// Flush database activity from memory pool to disk log
unsigned int nMinutes = 0;
@@ -332,13 +331,13 @@ void CDBEnv::CloseDb(const string& strFile)
{
{
LOCK(cs_db);
if (mapDb[strFile] != NULL)
if (mapDb[strFile] != nullptr)
{
// Close the database handle
Db* pdb = mapDb[strFile];
pdb->close(0);
delete pdb;
mapDb[strFile] = NULL;
mapDb[strFile] = nullptr;
}
}
}
@@ -348,7 +347,7 @@ bool CDBEnv::RemoveDb(const string& strFile)
this->CloseDb(strFile);
LOCK(cs_db);
int rc = dbenv.dbremove(NULL, strFile.c_str(), NULL, DB_AUTO_COMMIT);
int rc = dbenv.dbremove(nullptr, strFile.c_str(), nullptr, DB_AUTO_COMMIT);
return (rc == 0);
}
@@ -372,7 +371,7 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip)
CDB db(strFile.c_str(), "r");
Db* pdbCopy = new Db(&bitdb.dbenv, 0);
int ret = pdbCopy->open(NULL, // Txn pointer
int ret = pdbCopy->open(nullptr, // Txn pointer
strFileRes.c_str(), // Filename
"main", // Logical db name
DB_BTREE, // Database type
@@ -413,7 +412,7 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip)
}
Dbt datKey(&ssKey[0], ssKey.size());
Dbt datValue(&ssValue[0], ssValue.size());
int ret2 = pdbCopy->put(NULL, &datKey, &datValue, DB_NOOVERWRITE);
int ret2 = pdbCopy->put(nullptr, &datKey, &datValue, DB_NOOVERWRITE);
if (ret2 > 0)
fSuccess = false;
}
@@ -429,10 +428,10 @@ bool CDB::Rewrite(const string& strFile, const char* pszSkip)
if (fSuccess)
{
Db dbA(&bitdb.dbenv, 0);
if (dbA.remove(strFile.c_str(), NULL, 0))
if (dbA.remove(strFile.c_str(), nullptr, 0))
fSuccess = false;
Db dbB(&bitdb.dbenv, 0);
if (dbB.rename(strFileRes.c_str(), NULL, strFile.c_str(), 0))
if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(), 0))
fSuccess = false;
}
if (!fSuccess)
+17 -15
View File
@@ -7,6 +7,7 @@
#include "main.h"
#include <filesystem>
#include <map>
#include <string>
#include <vector>
@@ -28,6 +29,7 @@ extern unsigned int nWalletDBUpdated;
void ThreadFlushWalletDB(void* parg);
bool BackupWallet(const CWallet& wallet, const std::string& strDest);
bool AutoBackupWallet(const std::filesystem::path& walletPath);
class CDBEnv
@@ -36,7 +38,7 @@ private:
bool fDetachDB;
bool fDbEnvInit;
bool fMockDb;
boost::filesystem::path pathEnv;
std::filesystem::path pathEnv;
std::string strPath;
void EnvShutdown();
@@ -70,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);
@@ -82,10 +84,10 @@ public:
DbTxn *TxnBegin(int flags=DB_TXN_WRITE_NOSYNC)
{
DbTxn* ptxn = NULL;
int ret = dbenv.txn_begin(NULL, &ptxn, flags);
DbTxn* ptxn = nullptr;
int ret = dbenv.txn_begin(nullptr, &ptxn, flags);
if (!ptxn || ret != 0)
return NULL;
return nullptr;
return ptxn;
}
};
@@ -128,7 +130,7 @@ protected:
datValue.set_flags(DB_DBT_MALLOC);
int ret = pdb->get(activeTxn, &datKey, &datValue, 0);
memset(datKey.get_data(), 0, datKey.get_size());
if (datValue.get_data() == NULL)
if (datValue.get_data() == nullptr)
return false;
// Unserialize value
@@ -220,11 +222,11 @@ protected:
Dbc* GetCursor()
{
if (!pdb)
return NULL;
Dbc* pcursor = NULL;
int ret = pdb->cursor(NULL, &pcursor, 0);
return nullptr;
Dbc* pcursor = nullptr;
int ret = pdb->cursor(nullptr, &pcursor, 0);
if (ret != 0)
return NULL;
return nullptr;
return pcursor;
}
@@ -248,7 +250,7 @@ protected:
int ret = pcursor->get(&datKey, &datValue, fFlags);
if (ret != 0)
return ret;
else if (datKey.get_data() == NULL || datValue.get_data() == NULL)
else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr)
return 99999;
// Convert to streams
@@ -284,7 +286,7 @@ public:
if (!pdb || !activeTxn)
return false;
int ret = activeTxn->commit(0);
activeTxn = NULL;
activeTxn = nullptr;
return (ret == 0);
}
@@ -293,7 +295,7 @@ public:
if (!pdb || !activeTxn)
return false;
int ret = activeTxn->abort();
activeTxn = NULL;
activeTxn = nullptr;
return (ret == 0);
}
@@ -308,7 +310,7 @@ public:
return Write(std::string("version"), nVersion);
}
bool static Rewrite(const std::string& strFile, const char* pszSkip = NULL);
bool static Rewrite(const std::string& strFile, const char* pszSkip = nullptr);
};
@@ -316,7 +318,7 @@ public:
class CAddrDB
{
private:
boost::filesystem::path pathAddr;
std::filesystem::path pathAddr;
public:
CAddrDB();
bool Write(const CAddrMan& addr);
+223
View File
@@ -0,0 +1,223 @@
// Copyright (c) 2026 The Triangles developers
// Distributed under the MIT/X11 software license.
#include "hdwallet.h"
#include "bip39_english.h"
#include <cstring>
#include <algorithm>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <secp256k1.h>
namespace hd {
// ---- secp256k1 context (self-contained; independent of crypto_ecdsa) ------
static secp256k1_context* HDContext()
{
static secp256k1_context* ctx = NULL;
if (!ctx)
ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
return ctx;
}
static void HmacSha512(const unsigned char* key, size_t keylen,
const unsigned char* data, size_t datalen,
unsigned char out[64])
{
unsigned int len = 64;
HMAC(EVP_sha512(), key, (int)keylen, data, datalen, out, &len);
}
// Binary search the (lexicographically sorted) BIP39 English wordlist.
static int WordIndex(const std::string& w)
{
int lo = 0, hi = 2047;
while (lo <= hi) {
int mid = (lo + hi) / 2;
int c = w.compare(BIP39_WORDLIST_EN[mid]);
if (c == 0) return mid;
if (c < 0) hi = mid - 1; else lo = mid + 1;
}
return -1;
}
static std::vector<std::string> SplitWords(const std::string& s)
{
std::vector<std::string> out;
size_t i = 0, n = s.size();
while (i < n) {
while (i < n && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r')) i++;
size_t j = i;
while (j < n && !(s[j] == ' ' || s[j] == '\t' || s[j] == '\n' || s[j] == '\r')) j++;
if (j > i) out.push_back(s.substr(i, j - i));
i = j;
}
return out;
}
// ---- BIP39 ----------------------------------------------------------------
std::string GenerateMnemonic(int strengthBits)
{
if (strengthBits != 128 && strengthBits != 256) strengthBits = 256;
int entBytes = strengthBits / 8;
std::vector<unsigned char> ent(entBytes);
if (RAND_bytes(&ent[0], entBytes) != 1) return std::string();
// checksum = first (ENT/32) bits of SHA256(entropy)
unsigned char hash[32];
SHA256(&ent[0], entBytes, hash);
int csBits = strengthBits / 32;
// bit buffer = entropy || checksum bits
std::vector<unsigned char> bits = ent;
bits.push_back(hash[0]); // up to 8 checksum bits live in hash[0]
int totalBits = strengthBits + csBits;
int words = totalBits / 11;
std::string out;
for (int i = 0; i < words; i++) {
int idx = 0;
for (int b = 0; b < 11; b++) {
int bitpos = i * 11 + b;
int byte = bitpos / 8, off = 7 - (bitpos % 8);
int bit = (bits[byte] >> off) & 1;
idx = (idx << 1) | bit;
}
if (i) out += ' ';
out += BIP39_WORDLIST_EN[idx];
}
return out;
}
bool CheckMnemonic(const std::string& mnemonic)
{
std::vector<std::string> w = SplitWords(mnemonic);
size_t nw = w.size();
if (nw != 12 && nw != 15 && nw != 18 && nw != 21 && nw != 24) return false;
int totalBits = (int)nw * 11;
int csBits = totalBits / 33;
int entBits = totalBits - csBits;
if (entBits % 8 != 0) return false;
int entBytes = entBits / 8;
// unpack 11-bit indices into a bit buffer
std::vector<unsigned char> buf((totalBits + 7) / 8, 0);
for (size_t i = 0; i < nw; i++) {
int idx = WordIndex(w[i]);
if (idx < 0) return false;
for (int b = 0; b < 11; b++) {
int bit = (idx >> (10 - b)) & 1;
int bitpos = (int)i * 11 + b;
int byte = bitpos / 8, off = 7 - (bitpos % 8);
if (bit) buf[byte] |= (1 << off);
}
}
std::vector<unsigned char> ent(buf.begin(), buf.begin() + entBytes);
unsigned char hash[32];
SHA256(&ent[0], entBytes, hash);
// compare csBits checksum bits
for (int b = 0; b < csBits; b++) {
int bitpos = entBits + b;
int byte = bitpos / 8, off = 7 - (bitpos % 8);
int got = (buf[byte] >> off) & 1;
int want = (hash[b / 8] >> (7 - (b % 8))) & 1;
if (got != want) return false;
}
return true;
}
bool MnemonicToSeed(const std::string& mnemonic, const std::string& passphrase,
unsigned char seed64[64])
{
std::string salt = "mnemonic" + passphrase;
int rc = PKCS5_PBKDF2_HMAC(mnemonic.c_str(), (int)mnemonic.size(),
(const unsigned char*)salt.c_str(), (int)salt.size(),
2048, EVP_sha512(), 64, seed64);
return rc == 1;
}
// ---- BIP32 ----------------------------------------------------------------
bool MasterFromSeed(const unsigned char* seed, size_t seedlen, ExtKey& out)
{
unsigned char I[64];
HmacSha512((const unsigned char*)"Bitcoin seed", 12, seed, seedlen, I);
memcpy(out.key, I, 32);
memcpy(out.chaincode, I + 32, 32);
if (!secp256k1_ec_seckey_verify(HDContext(), out.key)) return false;
out.valid = true;
return true;
}
bool CKDpriv(const ExtKey& parent, uint32_t index, ExtKey& child)
{
if (!parent.valid) return false;
secp256k1_context* ctx = HDContext();
unsigned char data[37];
size_t dlen = 0;
if (index & HARDENED) {
data[0] = 0x00;
memcpy(data + 1, parent.key, 32);
dlen = 33;
} else {
// serP(point(parent.key)) = 33-byte compressed pubkey
secp256k1_pubkey pk;
if (!secp256k1_ec_pubkey_create(ctx, &pk, parent.key)) return false;
size_t plen = 33;
secp256k1_ec_pubkey_serialize(ctx, data, &plen, &pk, SECP256K1_EC_COMPRESSED);
dlen = 33;
}
data[dlen + 0] = (index >> 24) & 0xff;
data[dlen + 1] = (index >> 16) & 0xff;
data[dlen + 2] = (index >> 8) & 0xff;
data[dlen + 3] = index & 0xff;
dlen += 4;
unsigned char I[64];
HmacSha512(parent.chaincode, 32, data, dlen, I);
memcpy(child.key, parent.key, 32);
// child = (IL + parent) mod n ; rejects invalid (IL>=n or result 0)
if (!secp256k1_ec_seckey_tweak_add(ctx, child.key, I)) return false;
memcpy(child.chaincode, I + 32, 32);
child.valid = true;
return true;
}
bool DerivePath(const ExtKey& master, const std::vector<uint32_t>& path, ExtKey& out)
{
ExtKey cur = master;
for (size_t i = 0; i < path.size(); i++) {
ExtKey nxt;
if (!CKDpriv(cur, path[i], nxt)) return false;
cur = nxt;
}
out = cur;
return true;
}
bool DeriveTriangles(const std::string& mnemonic, const std::string& passphrase,
uint32_t account, uint32_t change, uint32_t index,
unsigned char privOut[32])
{
unsigned char seed[64];
if (!MnemonicToSeed(mnemonic, passphrase, seed)) return false;
ExtKey master;
if (!MasterFromSeed(seed, 64, master)) return false;
std::vector<uint32_t> path;
path.push_back(44u | HARDENED);
path.push_back(TRI_COIN_TYPE | HARDENED);
path.push_back(account | HARDENED);
path.push_back(change);
path.push_back(index);
ExtKey leaf;
if (!DerivePath(master, path, leaf)) return false;
memcpy(privOut, leaf.key, 32);
return true;
}
} // namespace hd
+50
View File
@@ -0,0 +1,50 @@
// Copyright (c) 2026 The Triangles developers
// Distributed under the MIT/X11 software license.
//
// Native BIP39 (mnemonic) + BIP32 (HD) key derivation for Triangles.
// Produces keys identical to the TRIdock web wallet (derivation path
// m/44'/2222'/0'/0/i, coin type 2222), so a 24-word phrase round-trips
// between the Qt/daemon wallet and the web wallet.
#ifndef TRIANGLES_HDWALLET_H
#define TRIANGLES_HDWALLET_H
#include <string>
#include <vector>
#include <cstdint>
#include <cstddef>
namespace hd {
static const uint32_t HARDENED = 0x80000000u;
static const uint32_t TRI_COIN_TYPE = 2222u; // matches triWallet.js
// A BIP32 extended private key (private scalar + chain code).
struct ExtKey {
unsigned char key[32];
unsigned char chaincode[32];
bool valid;
ExtKey() : valid(false) { }
};
// ---- BIP39 ----------------------------------------------------------------
// Generate a new mnemonic. strengthBits must be 128 (12 words) or 256 (24).
std::string GenerateMnemonic(int strengthBits = 256);
// Validate word membership + checksum.
bool CheckMnemonic(const std::string& mnemonic);
// PBKDF2-HMAC-SHA512(mnemonic, "mnemonic"+passphrase, 2048) -> 64-byte seed.
bool MnemonicToSeed(const std::string& mnemonic, const std::string& passphrase,
unsigned char seed64[64]);
// ---- BIP32 ----------------------------------------------------------------
bool MasterFromSeed(const unsigned char* seed, size_t seedlen, ExtKey& out);
bool CKDpriv(const ExtKey& parent, uint32_t index, ExtKey& child);
bool DerivePath(const ExtKey& master, const std::vector<uint32_t>& path, ExtKey& out);
// ---- High level -----------------------------------------------------------
// Derive the 32-byte private scalar for m/44'/coinType'/account'/change/index.
bool DeriveTriangles(const std::string& mnemonic, const std::string& passphrase,
uint32_t account, uint32_t change, uint32_t index,
unsigned char privOut[32]);
} // namespace hd
#endif // TRIANGLES_HDWALLET_H
+264 -73
View File
@@ -14,6 +14,8 @@
#include "smessage.h"
#include "openssl_compat.h"
#include "bootstrap.h"
#include "utxosnapshot.h"
#include "snapshotnet.h"
#include "tor/tor_embedded.h"
#include "tor/onion_v3.h"
#include "tor/tor_process.h"
@@ -22,37 +24,48 @@
#endif
#include "notificationqueue.h"
#include "addressindex.h"
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
// boost/filesystem/convenience.hpp removed in modern Boost; functionality is in filesystem.hpp
#include "chaindb_migrate.h"
#include <memory>
#include <thread>
#include <vector>
#include <filesystem>
#include <fstream>
#include <boost/interprocess/sync/file_lock.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <openssl/crypto.h>
#ifndef WIN32
#include <signal.h>
#endif
// Windows.h (transitively included) defines these as macros, clobbering Checkpoints:: enum values.
#ifdef STRICT
#undef STRICT
#endif
#ifdef ADVISORY
#undef ADVISORY
#endif
#ifdef PERMISSIVE
#undef PERMISSIVE
#endif
using namespace std;
using namespace boost;
namespace fs = boost::filesystem;
namespace fs = std::filesystem;
CWallet* pwalletMain;
std::unique_ptr<CWallet> pwalletMain;
CClientUIInterface uiInterface;
std::string strWalletFileName;
bool fConfChange;
bool fEnforceCanonical;
unsigned int nNodeLifespan;
unsigned int nDerivationMethodIndex;
//unsigned int nMinerSleep;
bool fUseFastIndex;
enum Checkpoints::CPMode CheckpointsMode;
static CCriticalSection cs_DeferredStartup;
static bool fDeferredStartupRunning = false;
static boost::thread_group* pScriptCheckThreads = NULL;
static std::unique_ptr<std::vector<std::thread>> pScriptCheckThreads;
static void ThreadScriptCheck()
{
@@ -97,7 +110,7 @@ fRequestShutdown = true;
uiInterface.QueueShutdown();
#else
// Without UI, Shutdown() can simply be started in a new thread
NewThread(Shutdown, NULL);
NewThread(Shutdown, nullptr);
#endif
}
@@ -107,6 +120,31 @@ bool ShutdownRequested()
return fRequestShutdown;
}
// P2P UTXO snapshot fetcher. Started from AppInit2 step 11.6 when the chain
// is empty and snapshot mode is enabled. Saves utxo-snapshot.bin on success
// and requests shutdown so a fresh boot can load it via Step 6c.
static void ThreadSnapshotFetch(void* parg)
{
RenameThread("Triangles-snapfetch");
// Give peers ~30s to connect and complete version handshake.
for (int i = 0; i < 30 && !fRequestShutdown; ++i)
MilliSleep(1000);
if (fRequestShutdown) return;
int snapTimeoutSec = (int)GetArg("-snapshottimeout", 600);
printf("SnapshotNet: starting P2P snapshot fetch (timeout=%ds)...\n", snapTimeoutSec);
std::string err;
if (SnapshotNet::TryFetchSnapshot(GetDataDir(), snapTimeoutSec, err)) {
printf("SnapshotNet: snapshot saved. Shutting down — restart the daemon to load it.\n");
uiInterface.InitMessage(_("UTXO snapshot saved. Restart the node to load it."));
StartShutdown();
} else {
printf("SnapshotNet: P2P snapshot fetch failed: %s\n", err.c_str());
printf("SnapshotNet: falling back to genesis sync. Use -bootstrap for legacy HTTP fallback.\n");
}
}
void ThreadDeferredStartup(void* parg)
{
// Make this thread recognisable as the deferred startup worker.
@@ -141,7 +179,7 @@ void ThreadDeferredStartup(void* parg)
}
catch (...)
{
PrintExceptionContinue(NULL, "ThreadDeferredStartup()");
PrintExceptionContinue(nullptr, "ThreadDeferredStartup()");
}
{
@@ -196,12 +234,11 @@ void Shutdown(void* parg)
pScriptCheckQueue->Quit();
if (pScriptCheckThreads)
{
pScriptCheckThreads->join_all();
delete pScriptCheckThreads;
pScriptCheckThreads = NULL;
for (std::thread& t : *pScriptCheckThreads)
if (t.joinable()) t.join();
pScriptCheckThreads.reset();
}
delete pScriptCheckQueue;
pScriptCheckQueue = NULL;
pScriptCheckQueue.reset();
}
// NOW safe to destroy Tor state - all threads have stopped
@@ -213,24 +250,24 @@ void Shutdown(void* parg)
{
pzmqNotifier->Shutdown();
delete pzmqNotifier;
pzmqNotifier = NULL;
pzmqNotifier = nullptr;
}
#endif
if (pNotificationQueue)
{
delete pNotificationQueue;
pNotificationQueue = NULL;
pNotificationQueue = nullptr;
}
// CTxDB().Close();
// MakeChainDB()->Close();
bitdb.Flush(false);
bitdb.Flush(true);
fs::remove(GetPidFile());
UnregisterWallet(pwalletMain);
delete pwalletMain;
UnregisterWallet(pwalletMain.get());
pwalletMain.reset();
// DB is flushed and wallet saved - safe to force-exit if something hangs
NewThread(ExitTimeout, NULL);
NewThread(ExitTimeout, nullptr);
MilliSleep(50);
printf("Triangles exited\n\n");
fExit = true;
@@ -280,7 +317,7 @@ bool AppInit(int argc, char* argv[])
if (!fs::is_directory(GetDataDir(false)))
{
fprintf(stderr, "Error: Specified directory does not exist\n");
Shutdown(NULL);
Shutdown(nullptr);
}
ReadConfigFile(mapArgs, mapMultiArgs);
@@ -302,7 +339,7 @@ bool AppInit(int argc, char* argv[])
// Command-line RPC
for (int i = 1; i < argc; i++)
if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "Triangles:"))
if (!IsSwitchChar(argv[i][0]) && !std::equal(std::begin("Triangles:"), std::end("Triangles:") - 1, argv[i], [](char a, char b) { return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b)); }))
fCommandLine = true;
if (fCommandLine)
@@ -316,10 +353,10 @@ bool AppInit(int argc, char* argv[])
catch (std::exception& e) {
PrintException(&e, "AppInit()");
} catch (...) {
PrintException(NULL, "AppInit()");
PrintException(nullptr, "AppInit()");
}
if (!fRet)
Shutdown(NULL);
Shutdown(nullptr);
return fRet;
}
@@ -380,7 +417,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" +
@@ -442,7 +479,6 @@ std::string HelpMessage()
" -walletnotify=<cmd> " + _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)") + "\n" +
" -confchange " + _("Require a confirmations for change (default: 0)") + "\n" +
" -enforcecanonical " + _("Enforce transaction scripts to use canonical PUSH operators (default: 1)") + "\n" +
" -alertnotify=<cmd> " + _("Execute command when a relevant alert is received (%s in cmd is replaced by message)") + "\n" +
" -upgradewallet " + _("Upgrade wallet to latest format") + "\n" +
" -keypool=<n> " + _("Set key pool size to <n> (default: 100)") + "\n" +
" -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" +
@@ -505,7 +541,7 @@ bool AppInit2()
#ifdef _MSC_VER
// Turn off Microsoft heap dump noise
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0));
_CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, 0));
#endif
#if _MSC_VER >= 1400
// Disable confusing "helpful" text message on abort, Ctrl-C
@@ -522,7 +558,7 @@ bool AppInit2()
#endif
typedef BOOL (WINAPI *PSETPROCDEPPOL)(DWORD);
PSETPROCDEPPOL setProcDEPPol = (PSETPROCDEPPOL)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetProcessDEPPolicy");
if (setProcDEPPol != NULL) setProcDEPPol(PROCESS_DEP_ENABLE);
if (setProcDEPPol != nullptr) setProcDEPPol(PROCESS_DEP_ENABLE);
#endif
#ifndef WIN32
umask(077);
@@ -532,15 +568,15 @@ bool AppInit2()
sa.sa_handler = HandleSIGTERM;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, nullptr);
sigaction(SIGINT, &sa, nullptr);
// Reopen debug.log on SIGHUP
struct sigaction sa_hup;
sa_hup.sa_handler = HandleSIGHUP;
sigemptyset(&sa_hup.sa_mask);
sa_hup.sa_flags = 0;
sigaction(SIGHUP, &sa_hup, NULL);
sigaction(SIGHUP, &sa_hup, nullptr);
#endif
// ********************************************************* Step 2: parameter interactions
@@ -550,7 +586,7 @@ bool AppInit2()
//nMinerSleep = GetArg("-minersleep", 500);
CheckpointsMode = Checkpoints::STRICT;
std::string strCpMode = GetArg("-cppolicy", "strict");
std::string strCpMode = GetArg(std::string_view{"-cppolicy"}, std::string_view{"strict"});
if(strCpMode == "strict")
CheckpointsMode = Checkpoints::STRICT;
@@ -664,15 +700,15 @@ bool AppInit2()
int nScriptCheckThreads = GetArg("-par", 0);
if (nScriptCheckThreads <= 0)
nScriptCheckThreads = boost::thread::hardware_concurrency();
nScriptCheckThreads = std::thread::hardware_concurrency();
if (nScriptCheckThreads > 16)
nScriptCheckThreads = 16;
if (nScriptCheckThreads > 1)
{
pScriptCheckQueue = new CCheckQueue<CScriptCheck>(128);
pScriptCheckThreads = new boost::thread_group();
pScriptCheckQueue = std::make_unique<CCheckQueue<CScriptCheck>>(32);
pScriptCheckThreads = std::make_unique<std::vector<std::thread>>();
for (int i = 0; i < nScriptCheckThreads - 1; ++i)
pScriptCheckThreads->create_thread(&ThreadScriptCheck);
pScriptCheckThreads->emplace_back(&ThreadScriptCheck);
printf("Script verification threads: %d workers + main thread\n", nScriptCheckThreads - 1);
}
@@ -692,7 +728,7 @@ bool AppInit2()
return InitError(_("Initialization sanity check failed. Triangles is shutting down."));
std::string strDataDir = GetDataDir().string();
std::string strWalletFileName = GetArg("-wallet", "wallet.dat");
std::string strWalletFileName = GetArg(std::string_view{"-wallet"}, std::string_view{"wallet.dat"});
// strWalletFileName must be a plain filename without a directory
if (strWalletFileName != fs::path(strWalletFileName).stem().string() + fs::path(strWalletFileName).extension().string())
@@ -876,7 +912,7 @@ bool AppInit2()
if (mapArgs.count("-checkpointkey")) // triangles: checkpoint master priv key
{
if (!Checkpoints::SetCheckpointPrivKey(GetArg("-checkpointkey", "")))
if (!Checkpoints::SetCheckpointPrivKey(GetArg(std::string_view{"-checkpointkey"}, std::string_view{""})))
InitError(_("Unable to sign checkpoint, wrong checkpointkey?\n"));
}
@@ -887,17 +923,27 @@ bool AppInit2()
// ********************************************************* Step 6b: bootstrap download (daemon)
// Automatic: if data dir has no blockchain, bootstrap without asking.
// Can also be forced with -bootstrap flag, or disabled with -nobootstrap.
#ifndef QT_GUI
//
// 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.
// 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);
bool snapshotMode = GetBoolArg("-snapshot", true);
fs::path dataPath = GetDataDir();
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataPath);
if (needsBootstrap && !noBootstrap) {
if (needsBootstrap && !noBootstrap && !snapshotMode) {
printf("Bootstrap: no blockchain data found — downloading automatically.\n");
printf("Bootstrap: (use -nobootstrap to skip)\n");
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");
printf("Bootstrap: (use -bootstrap for legacy clearnet HTTP bootstrap, or -snapshot=0 to disable P2P fetcher)\n");
}
if (wantsBootstrap)
@@ -907,37 +953,97 @@ bool AppInit2()
std::string host = Bootstrap::DEFAULT_HOST;
std::string strError;
uiInterface.InitMessage(_("Downloading blockchain snapshot..."));
printf("Bootstrap: contacting %s...\n", host.c_str());
auto progressFn = [](int64_t bytesDownloaded, int64_t totalBytes) {
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);
}
}
};
bool success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError);
if (!success) {
host = Bootstrap::FALLBACK_HOST;
printf("\nBootstrap: primary host failed, trying fallback %s...\n", host.c_str());
success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError);
// 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(GetChainDataDir())) {
uiInterface.InitMessage(_("Downloading UTXO snapshot..."));
printf("Bootstrap: trying UTXO snapshot from %s (fast path)...\n", host.c_str());
std::string utxoError;
if (Bootstrap::DownloadUtxoSnapshot(host, dataPath, progressFn, utxoError)) {
printf("\nBootstrap: UTXO snapshot loaded — will sync remaining blocks from network.\n");
success = true;
} else {
printf("\nBootstrap: UTXO snapshot unavailable: %s\n", utxoError.c_str());
printf("Bootstrap: falling back to full bootstrap download...\n");
}
triedUtxoSnapshot = true;
}
// Fall back to full bootstrap.tar.gz if UTXO snapshot failed
if (!success) {
printf("\nBootstrap: failed: %s\n", strError.c_str());
printf("Bootstrap: skipping, will sync from network.\n");
} else {
printf("\nBootstrap: done.\n");
uiInterface.InitMessage(_("Downloading blockchain snapshot..."));
printf("Bootstrap: contacting %s...\n", host.c_str());
success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError);
if (!success) {
printf("\nBootstrap: failed: %s\n", strError.c_str());
printf("Bootstrap: skipping, will sync from network.\n");
} else {
printf("\nBootstrap: done.\n");
}
}
StartupPerfLog("bootstrap_download", GetTimeMillis() - nBootstrapStart,
strprintf("host=%s success=%d", host.c_str(), success));
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 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 chainDbDir = GetChainDataDir();
if (fs::exists(snapshotFile) && !fs::exists(chainDbDir)) {
printf("Found utxo-snapshot.bin — loading UTXO snapshot...\n");
uiInterface.InitMessage(_("Loading UTXO snapshot..."));
std::string strError;
if (UtxoSnapshot::LoadSnapshot(snapshotFile, dataPath, strError)) {
printf("UTXO snapshot loaded successfully.\n");
} else {
printf("UTXO snapshot load failed: %s\n", strError.c_str());
printf("Will proceed with normal sync.\n");
}
}
}
// ********************************************************* Step 6d: optional LevelDB -> RocksDB chain DB migration
if (GetBoolArg("-migratechaindb", false) || GetBoolArg("-migratechaindbforce", false))
{
uiInterface.InitMessage(_("Migrating chain database to RocksDB..."));
std::string strMigrateError;
bool fForce = GetBoolArg("-migratechaindbforce", false);
if (!MaybeMigrateLevelDbToRocksDb(fForce, strMigrateError))
return InitError(strprintf(_("Chain DB migration failed: %s"), strMigrateError.c_str()));
}
// ********************************************************* Step 7: load blockchain
@@ -951,22 +1057,65 @@ 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 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 chain database...\n");
uiInterface.InitMessage(_("Removing chain database for reindex..."));
WipeChainDataDir();
}
uiInterface.InitMessage(_("Loading block index..."));
printf("Loading block index...\n");
nStart = GetTimeMillis();
if (!LoadBlockIndex())
return InitError(_("Error loading blkindex.dat"));
// triangles fix (pitfall #61): initialize pindexFinalized from the
// hardcoded checkpoint on startup, BEFORE the daemon opens any peer
// connections or processes any block messages.
//
// Without this, pindexFinalized stays NULL on a fresh restart even when
// we have 2.2M blocks on disk, because the auto-checkpoint code in
// ActivateBestChain() at main.cpp:2459 only sets it when
// !IsInitialBlockDownload(). If the chain tip is more than 24h stale
// (which happens on every restart with a synced chain), IsInitialBlockDownload()
// returns true and pindexFinalized never gets set.
//
// The downstream reorg guard at main.cpp:2198 short-circuits when
// pindexFinalized is NULL, which allowed a 3,755-block minority fork
// to overwrite a healthy 2,206,004-block chain on 2026-06-16. Loading
// the hardcoded checkpoint from checkpoints.cpp (block 2,205,000) on
// startup means the reorg guard is always active whenever the
// checkpointed block is in our local mapBlockIndex.
{
CBlockIndex* pCheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex);
if (pCheckpoint && pCheckpoint != pindexFinalized)
{
pindexFinalized = pCheckpoint;
printf("STARTUP-CHECKPOINT: pindexFinalized set to block %d (%s) from hardcoded checkpoint\n",
pindexFinalized->nHeight, pindexFinalized->GetBlockHash().ToString().substr(0,20).c_str());
}
else if (!pCheckpoint)
{
printf("STARTUP-CHECKPOINT: WARNING — hardcoded checkpoint not in local block index, pindexFinalized remains NULL\n");
}
}
// 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..."));
@@ -1045,7 +1194,22 @@ bool AppInit2()
printf("Loading wallet...\n");
nStart = GetTimeMillis();
bool fFirstRun = true;
pwalletMain = new CWallet(strWalletFileName);
pwalletMain = std::make_unique<CWallet>(strWalletFileName);
// Auto-backup wallet.dat before loading (protects against corruption during load/flush)
{
fs::path walletPath = GetDataDir() / strWalletFileName;
if (fs::exists(walletPath)) {
uintmax_t wsize = fs::file_size(walletPath);
printf("Wallet file size: %llu bytes\n", (unsigned long long)wsize);
if (wsize < 1024) {
strErrors << _("WARNING: wallet.dat is suspiciously small (") << wsize << _(" bytes). It may be corrupt.\n");
printf("WARNING: wallet.dat is only %llu bytes - possibly corrupt!\n", (unsigned long long)wsize);
}
AutoBackupWallet(walletPath);
}
}
DBErrors nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun);
if (nLoadWalletRet != DB_LOAD_OK)
{
@@ -1074,9 +1238,9 @@ bool AppInit2()
int nMaxVersion = GetArg("-upgradewallet", 0);
if (nMaxVersion == 0) // the -upgradewallet without argument case
{
printf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
printf("Performing wallet upgrade to %i\n", static_cast<int>(WalletFeature::Latest));
nMaxVersion = CLIENT_VERSION;
pwalletMain->SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately
pwalletMain->SetMinVersion(WalletFeature::Latest); // permanently upgrade the wallet immediately
}
else
printf("Allowing wallet upgrade up to %i\n", nMaxVersion);
@@ -1102,7 +1266,7 @@ bool AppInit2()
printf(" wallet %15" PRId64 "ms\n", GetTimeMillis() - nStart);
StartupPerfLog("wallet_load", GetTimeMillis() - nStart, strprintf("firstrun=%d", fFirstRun));
RegisterWallet(pwalletMain);
RegisterWallet(pwalletMain.get());
CBlockIndex *pindexRescan = pindexBest;
if (GetBoolArg("-rescan"))
@@ -1124,7 +1288,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) &&
@@ -1222,6 +1386,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())
@@ -1292,7 +1465,7 @@ bool AppInit2()
// Launch background thread for Tor health monitoring and seeder maintenance
if (torStarted) {
if (!NewThread(ThreadTorMaintenance, NULL))
if (!NewThread(ThreadTorMaintenance, nullptr))
printf("Warning: ThreadTorMaintenance could not be started\n");
}
}
@@ -1360,31 +1533,49 @@ bool AppInit2()
printf("mapWallet.size() = %" PRIszu "\n", pwalletMain->mapWallet.size());
printf("mapAddressBook.size() = %" PRIszu "\n", pwalletMain->mapAddressBook.size());
if (!NewThread(StartNode, NULL))
if (!NewThread(StartNode, nullptr))
InitError(_("Error: could not start node"));
if (fServer)
NewThread(ThreadRPCServer, NULL);
NewThread(ThreadRPCServer, nullptr);
// ********************************************************* Step 11.6: P2P UTXO snapshot fetch
// If the chain is empty and snapshot mode is enabled (default), spawn a
// background thread that waits for snapshot-capable peers, downloads the
// canonical snapshot via P2P, and saves it to utxo-snapshot.bin. On
// success, requests a clean shutdown so the user can restart and have
// Step 6c load the snapshot in a fresh boot.
{
bool snapshotMode = GetBoolArg("-snapshot", true);
bool needsSnapshot = (nBestHeight <= 0);
bool haveSnapshotFile = fs::exists(GetDataDir() / "utxo-snapshot.bin");
if (snapshotMode && needsSnapshot && !haveSnapshotFile &&
Checkpoints::GetBestSnapshotHeight() > 0)
{
NewThread(ThreadSnapshotFetch, nullptr);
}
}
{
LOCK(cs_DeferredStartup);
fDeferredStartupRunning = true;
}
if (!NewThread(ThreadDeferredStartup, NULL))
if (!NewThread(ThreadDeferredStartup, nullptr))
{
printf("Warning: deferred startup thread could not be started, running inline\n");
ThreadDeferredStartup(NULL);
ThreadDeferredStartup(nullptr);
}
StartupPerfLog("start_services", GetTimeMillis() - nStart);
// ********************************************************* Step 11.5: ZMQ notifications
#ifdef ENABLE_ZMQ
{
std::string zmqAddr = GetArg("-zmqpubhashblock", "");
std::string zmqAddr = GetArg(std::string_view{"-zmqpubhashblock"}, std::string_view{""});
if (zmqAddr.empty())
zmqAddr = GetArg("-zmqpubhashtx", "");
zmqAddr = GetArg(std::string_view{"-zmqpubhashtx"}, std::string_view{""});
if (zmqAddr.empty())
zmqAddr = GetArg("-zmqpub", "");
zmqAddr = GetArg(std::string_view{"-zmqpub"}, std::string_view{""});
if (!zmqAddr.empty())
{
pzmqNotifier = new CZMQPublishNotifier();
@@ -1392,7 +1583,7 @@ bool AppInit2()
{
printf("ZMQ: Failed to initialize publisher on %s\n", zmqAddr.c_str());
delete pzmqNotifier;
pzmqNotifier = NULL;
pzmqNotifier = nullptr;
}
}
}
+2 -1
View File
@@ -7,8 +7,9 @@
#include "wallet.h"
#include "tor_embed_hooks.h"
#include <memory>
extern CWallet* pwalletMain;
extern std::unique_ptr<CWallet> pwalletMain;
extern std::string strWalletFileName;
void StartShutdown();
bool ShutdownRequested();
-405
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
+23 -7
View File
@@ -14,7 +14,7 @@ extern unsigned int nTargetSpacing;
// Set to 20-minute for production network
//unsigned int nModifierInterval = MODIFIER_INTERVAL;
typedef std::map<int, unsigned int> MapModifierCheckpoints;
using MapModifierCheckpoints = std::map<int, unsigned int>;
// Hard checkpoints of stake modifiers to ensure they are deterministic
static std::map<int, unsigned int> mapStakeModifierCheckpoints = {
@@ -31,9 +31,23 @@ int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd)
if (nAge < 0)
return 0;
// After v5 fork: remove max age cap so coins aged during the freeze can stake
// After v5 fork: use soft cap of 7 days instead of hard nStakeMaxAge.
// This prevents "stake surprise" where a whale who was offline for weeks
// comes back with massively amplified staking power and dominates blocks.
// The 7-day cap still allows generous accumulation while limiting abuse.
static const int64_t STAKE_AGE_SOFT_CAP = 7 * 24 * 60 * 60; // 7 days
// Activation gate: the soft cap shipped 2026-04-20 without a height/time
// gate, retroactively invalidating earlier blocks staked with long-aged
// coins (e.g. coins idle through the 2022-2026 freeze). Apply the cap
// only to stakes after the activation timestamp; historical stakes
// validate under the rules they were created with (uncapped age).
static const int64_t STAKE_AGE_SOFT_CAP_ACTIVATION = 1776000000; // 2026-04-12 ~13:20 UTC
if (pindexBest && pindexBest->nHeight >= FORK_HEIGHT_V5)
{
if (nIntervalEnd >= STAKE_AGE_SOFT_CAP_ACTIVATION)
return min(nAge, STAKE_AGE_SOFT_CAP);
return nAge;
}
return min(nAge, (int64_t)nStakeMaxAge);
}
@@ -334,9 +348,11 @@ bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned
// Now check if proof-of-stake hash meets target protocol
if (CBigNum(hashProofOfStake) > bnCoinDayWeight * bnTargetPerCoinDay)
{
// Guard against null pindexBest during early startup / IBD
int nCurrentHeight = pindexBest ? pindexBest->nHeight : 0;
// triangles fix: accept hash to get blockchain moving again with Pharao release (v 4.0.0.1) for first 10 blocks after release
//printf(">>>> pindexBest->nHeight %d\n",pindexBest->nHeight);
if (pindexBest->nHeight > CRAPCHAIN_CUTOFF_BLOCK)
if (nCurrentHeight > CRAPCHAIN_CUTOFF_BLOCK)
{
if(fDebug)
{
@@ -349,8 +365,8 @@ bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned
else
{
//accept hash
if (pindexBest->nHeight % 10000 == 0 || pindexBest->nHeight > 2186900)
printf(">>>> pindexBest->nHeight %d, Pharao release - hash accepted\n", pindexBest->nHeight);
if (nCurrentHeight % 10000 == 0 || nCurrentHeight > 2186900)
printf(">>>> pindexBest->nHeight %d, Pharao release - hash accepted\n", nCurrentHeight);
}
}
@@ -380,7 +396,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;
}
+10 -11
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;
@@ -70,7 +68,7 @@ public:
CPubKey() { }
CPubKey(const std::vector<unsigned char> &vchPubKeyIn) : vchPubKey(vchPubKeyIn) { }
friend bool operator==(const CPubKey &a, const CPubKey &b) { return a.vchPubKey == b.vchPubKey; }
friend bool operator!=(const CPubKey &a, const CPubKey &b) { return a.vchPubKey != b.vchPubKey; }
friend bool operator!=(const CPubKey &a, const CPubKey &b) = default;
friend bool operator<(const CPubKey &a, const CPubKey &b) { return a.vchPubKey < b.vchPubKey; }
IMPLEMENT_SERIALIZE(
@@ -101,24 +99,25 @@ public:
// secure_allocator is defined in allocators.h
// CPrivKey is a serialized private key, with all parameters included (279 bytes)
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CPrivKey;
// CSecret is a serialization of just the secret parameter (32 bytes)
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CSecret;
using CPrivKey = std::vector<unsigned char, secure_allocator<unsigned char>>;
using CSecret = std::vector<unsigned char, secure_allocator<unsigned char>>;
/** An encapsulated OpenSSL Elliptic Curve key (public and/or private) */
/** An encapsulated secp256k1 elliptic-curve key (public and/or private). */
class CKey
{
protected:
EC_KEY* pkey;
// 32-byte private scalar. Valid iff fSet && fHavePrivKey.
unsigned char vch[32];
// Cached serialized public key (33 or 65 bytes). Valid iff fSet.
std::vector<unsigned char> vchPubKey;
bool fSet;
bool fCompressedPubKey;
bool fHavePrivKey;
public:
void SetCompressedPubKey();
void SetUnCompressedPubKey();
EC_KEY* GetECKey();
void Reset();
CKey();
+14 -18
View File
@@ -50,10 +50,9 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut)
{
{
LOCK(cs_KeyStore);
ScriptMap::const_iterator mi = mapScripts.find(hash);
if (mi != mapScripts.end())
if (auto mi = mapScripts.find(hash); mi != mapScripts.end())
{
redeemScriptOut = (*mi).second;
redeemScriptOut = mi->second;
return true;
}
}
@@ -94,20 +93,19 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
if (!SetCrypted())
return false;
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
for (; mi != mapCryptedKeys.end(); ++mi)
for (const auto& [pubKeyHash, val] : mapCryptedKeys)
{
const CPubKey &vchPubKey = (*mi).second.first;
const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
const CPubKey &vchPubKey = val.first;
const std::vector<unsigned char> &vchCryptedSecret = val.second;
CSecret vchSecret;
if(!DecryptSecret(vMasterKeyIn, vchCryptedSecret, vchPubKey.GetHash(), vchSecret))
return false;
if (vchSecret.size() != 32)
return false;
CKey key;
key.SetPubKey(vchPubKey);
key.SetSecret(vchSecret);
if (key.GetPubKey() == vchPubKey)
CKey decryptedKey;
decryptedKey.SetPubKey(vchPubKey);
decryptedKey.SetSecret(vchSecret);
if (decryptedKey.GetPubKey() == vchPubKey)
break;
return false;
}
@@ -159,11 +157,10 @@ bool CCryptoKeyStore::GetKey(const CKeyID &address, CKey& keyOut) const
if (!IsCrypted())
return CBasicKeyStore::GetKey(address, keyOut);
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
if (mi != mapCryptedKeys.end())
if (auto mi = mapCryptedKeys.find(address); mi != mapCryptedKeys.end())
{
const CPubKey &vchPubKey = (*mi).second.first;
const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
const CPubKey &vchPubKey = mi->second.first;
const std::vector<unsigned char> &vchCryptedSecret = mi->second.second;
CSecret vchSecret;
if (!DecryptSecret(vMasterKey, vchCryptedSecret, vchPubKey.GetHash(), vchSecret))
return false;
@@ -184,10 +181,9 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
if (!IsCrypted())
return CKeyStore::GetPubKey(address, vchPubKeyOut);
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
if (mi != mapCryptedKeys.end())
if (auto mi = mapCryptedKeys.find(address); mi != mapCryptedKeys.end())
{
vchPubKeyOut = (*mi).second.first;
vchPubKeyOut = mi->second.first;
return true;
}
}
+12 -16
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;
@@ -44,8 +44,8 @@ public:
}
};
typedef std::map<CKeyID, std::pair<CSecret, bool> > KeyMap;
typedef std::map<CScriptID, CScript > ScriptMap;
using KeyMap = std::map<CKeyID, std::pair<CSecret, bool>>;
using ScriptMap = std::map<CScriptID, CScript>;
/** Basic key store, that keeps keys in an address->secret map */
class CBasicKeyStore : public CKeyStore
@@ -70,11 +70,9 @@ public:
setAddress.clear();
{
LOCK(cs_KeyStore);
KeyMap::const_iterator mi = mapKeys.begin();
while (mi != mapKeys.end())
for (const auto& [key, val] : mapKeys)
{
setAddress.insert((*mi).first);
mi++;
setAddress.insert(key);
}
}
}
@@ -82,11 +80,10 @@ public:
{
{
LOCK(cs_KeyStore);
KeyMap::const_iterator mi = mapKeys.find(address);
if (mi != mapKeys.end())
if (auto mi = mapKeys.find(address); mi != mapKeys.end())
{
keyOut.Reset();
keyOut.SetSecret((*mi).second.first, (*mi).second.second);
keyOut.SetSecret(mi->second.first, mi->second.second);
return true;
}
}
@@ -97,7 +94,7 @@ public:
virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const;
};
typedef std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char> > > CryptedKeyMap;
using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
/** Keystore which keeps the private keys encrypted.
* It derives from the basic key store, which is used if no encryption is active.
@@ -160,24 +157,23 @@ public:
bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
void GetKeys(std::set<CKeyID> &setAddress) const
{
LOCK(cs_KeyStore);
if (!IsCrypted())
{
CBasicKeyStore::GetKeys(setAddress);
return;
}
setAddress.clear();
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
while (mi != mapCryptedKeys.end())
for (const auto& [key, val] : mapCryptedKeys)
{
setAddress.insert((*mi).first);
mi++;
setAddress.insert(key);
}
}
/* Wallet status (encrypted, locked) changed.
* Note: Called without locks held.
*/
boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged;
CSignal<void(CCryptoKeyStore*)> NotifyStatusChanged;
};
#endif
+1105 -747
View File
File diff suppressed because it is too large Load Diff
+217 -115
View File
@@ -12,8 +12,11 @@
#include "scrypt.h"
#include "hashblock.h"
#include "checkqueue.h"
#include "sigcache.h"
#include <list>
#include <array>
#include <memory>
class CWallet;
class CBlock;
@@ -28,28 +31,29 @@ class CRequestTracker;
class CNode;
class CScriptCheck;
static const int CUTOFF_POW_BLOCK = 9000;
static const int CRAPCHAIN_CUTOFF_BLOCK = 17691; // pre-Pharao (version 4) blockchain until block 17691
static const int FORK_HEIGHT_V5 = 17651; // v5 hard fork: decentralization + Tor v3 (next block after last checkpoint)
static const int FORK_HEIGHT_V5_4 = 2186941; // v5.4: tighter timestamps, deterministic fork resolution
constexpr int CUTOFF_POW_BLOCK = 9000;
constexpr int CRAPCHAIN_CUTOFF_BLOCK = 17691; // pre-Pharao (version 4) blockchain until block 17691
constexpr int FORK_HEIGHT_V5 = 17651; // v5 hard fork: decentralization + Tor v3 (next block after last checkpoint)
constexpr int FORK_HEIGHT_V5_4 = 2186941; // v5.4: tighter timestamps, deterministic fork resolution
static const unsigned int MAX_BLOCK_SIZE = 1000000;
static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
static const unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50;
static const unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100;
static const unsigned int MAX_ORPHAN_BLOCKS = 750;
static const unsigned int MAX_ORPHAN_BLOCKS_IBD = 4000;
static const unsigned int MAX_INV_SZ = 50000;
static const int64_t MIN_TX_FEE = (1 * CENT) / 100;
static const int64_t MIN_RELAY_TX_FEE = (1 * CENT) / 100;
static const int64_t MAX_MONEY = 2222222 * COIN;
static const int64_t COIN_YEAR_REWARD = 33 * CENT; // 33% per year
static const int64_t MAX_TRI_PROOF_OF_STAKE = 0.33 * COIN;
static const int MODIFIER_INTERVAL_SWITCH = 1;
constexpr unsigned int MAX_BLOCK_SIZE = 1000000;
constexpr unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
constexpr unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50;
constexpr unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100;
constexpr unsigned int MAX_ORPHAN_BLOCKS = 750;
constexpr unsigned int MAX_ORPHAN_BLOCKS_IBD = 1500;
constexpr unsigned int MAX_REORG_DEPTH = 100; // reject reorgs deeper than this (finality)
constexpr unsigned int MAX_INV_SZ = 50000;
constexpr int64_t MIN_TX_FEE = (1 * CENT) / 100;
constexpr int64_t MIN_RELAY_TX_FEE = (1 * CENT) / 100;
constexpr int64_t MAX_MONEY = 2222222 * COIN;
constexpr int64_t COIN_YEAR_REWARD = 33 * CENT; // 33% per year
constexpr int64_t MAX_TRI_PROOF_OF_STAKE = 0.33 * COIN;
constexpr int MODIFIER_INTERVAL_SWITCH = 1;
inline bool MoneyRange(int64_t nValue) { return (nValue >= 0 && nValue <= MAX_MONEY); }
// Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp.
static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC
constexpr unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC
#ifdef USE_UPNP
static const int fHaveUPnP = true;
@@ -59,10 +63,10 @@ static const int fHaveUPnP = false;
static const uint256 hashGenesisBlockOfficial("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
static const uint256 hashGenesisBlockTestNet ("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 3 * 60 : 10 * 60; }
inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 90 : 10 * 60; }
inline int64_t PastDrift(int64_t nTime, int nHeight) { return nTime - GetMaxTimeDrift(nHeight); }
inline int64_t FutureDrift(int64_t nTime, int nHeight) { return nTime + GetMaxTimeDrift(nHeight); }
// Height-less overloads always use post-V5.4 rules (3-min drift).
// Height-less overloads always use post-V5.4 rules (90-second drift).
// All nodes are well past FORK_HEIGHT_V5_4; using the global nBestHeight
// here previously caused nodes at different heights to disagree on block
// validity during the fork transition — a consensus-splitting bug.
@@ -83,6 +87,7 @@ extern uint256 nBestChainTrust;
extern uint256 nBestInvalidTrust;
extern uint256 hashBestChain;
extern CBlockIndex* pindexBest;
extern CBlockIndex* pindexFinalized; // auto-checkpoint: deepest finalized block
extern unsigned int nTransactionsUpdated;
extern uint64_t nLastBlockTx;
extern uint64_t nLastBlockSize;
@@ -92,7 +97,7 @@ extern int64_t nTimeBestReceived;
extern CCriticalSection cs_setpwalletRegistered;
extern std::set<CWallet*> setpwalletRegistered;
extern unsigned char pchMessageStart[4];
extern std::map<uint256, CBlock*> mapOrphanBlocks;
extern std::map<uint256, std::unique_ptr<CBlock>> mapOrphanBlocks;
// Settings
extern int64_t nTransactionFee;
@@ -104,15 +109,15 @@ extern unsigned int nDerivationMethodIndex;
extern bool fEnforceCanonical;
// Minimum disk space required - used in CheckDiskSpace()
static const uint64_t nMinDiskSpace = 52428800;
constexpr uint64_t nMinDiskSpace = 52428800;
class CReserveKey;
class CTxDB;
class CTxDBBase;
class CTxIndex;
void RegisterWallet(CWallet* pwalletIn);
void UnregisterWallet(CWallet* pwalletIn);
void SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false, bool fConnect = true);
void SyncWithWallets(const CTransaction& tx, const CBlock* pblock = nullptr, bool fUpdate = false, bool fConnect = true);
bool ProcessBlock(CNode* pfrom, CBlock* pblock);
bool CheckDiskSpace(uint64_t nAdditionalBytes=0);
FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode="rb");
@@ -132,10 +137,11 @@ int64_t GetProofOfStakeReward(int64_t nCoinAge, int64_t nFees);
unsigned int ComputeMinWork(unsigned int nBase, int64_t nTime);
unsigned int ComputeMinStake(unsigned int nBase, int64_t nTime, unsigned int nBlockTime);
int GetNumBlocksOfPeers();
bool IsInitialBlockDownload();
[[nodiscard]] bool IsInitialBlockDownload();
std::string GetWarnings(std::string strFor);
bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock);
uint256 WantedByOrphan(const CBlock* pblockOrphan);
unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans);
const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake);
void StakeMiner(CWallet *pwallet);
void ResendWalletTransactions(bool fForce = false);
@@ -182,10 +188,7 @@ public:
a.nTxPos == b.nTxPos);
}
friend bool operator!=(const CDiskTxPos& a, const CDiskTxPos& b)
{
return !(a == b);
}
friend bool operator!=(const CDiskTxPos& a, const CDiskTxPos& b) = default;
std::string ToString() const
@@ -213,8 +216,8 @@ public:
CInPoint() { SetNull(); }
CInPoint(CTransaction* ptxIn, unsigned int nIn) { ptx = ptxIn; n = nIn; }
void SetNull() { ptx = NULL; n = (unsigned int) -1; }
bool IsNull() const { return (ptx == NULL && n == (unsigned int) -1); }
void SetNull() { ptx = nullptr; n = (unsigned int) -1; }
bool IsNull() const { return (ptx == nullptr && n == (unsigned int) -1); }
};
@@ -242,10 +245,7 @@ public:
return (a.hash == b.hash && a.n == b.n);
}
friend bool operator!=(const COutPoint& a, const COutPoint& b)
{
return !(a == b);
}
friend bool operator!=(const COutPoint& a, const COutPoint& b) = default;
std::string ToString() const
{
@@ -310,10 +310,7 @@ public:
a.nSequence == b.nSequence);
}
friend bool operator!=(const CTxIn& a, const CTxIn& b)
{
return !(a == b);
}
friend bool operator!=(const CTxIn& a, const CTxIn& b) = default;
std::string ToStringShort() const
{
@@ -403,10 +400,7 @@ public:
a.scriptPubKey == b.scriptPubKey);
}
friend bool operator!=(const CTxOut& a, const CTxOut& b)
{
return !(a == b);
}
friend bool operator!=(const CTxOut& a, const CTxOut& b) = default;
std::string ToStringShort() const
{
@@ -430,11 +424,11 @@ public:
enum GetMinFee_mode
enum class GetMinFeeMode : int
{
GMF_BLOCK,
GMF_RELAY,
GMF_SEND,
Block,
Relay,
Send,
};
/** A single unspent transaction output entry in the UTXO database.
@@ -481,7 +475,7 @@ public:
}
};
typedef std::map<COutPoint, CUtxoEntry> MapPrevTx;
using MapPrevTx = std::map<COutPoint, CUtxoEntry>;
/** The basic transaction that is broadcasted on the network and contained in
* blocks. A transaction can contain multiple inputs and outputs.
@@ -595,7 +589,7 @@ public:
/** Check for standard transaction types
@return True if all outputs (scriptPubKeys) use only standard transaction forms
*/
bool IsStandard() const;
[[nodiscard]] bool IsStandard() const;
/** Check for standard transaction types
@param[in] mapInputs Map of previous transactions that have outputs we're spending
@@ -643,9 +637,9 @@ public:
*/
int64_t GetValueIn(const MapPrevTx& mapInputs) const;
int64_t GetMinFee(unsigned int nBlockSize=1, enum GetMinFee_mode mode=GMF_BLOCK, unsigned int nBytes = 0) const;
int64_t GetMinFee(unsigned int nBlockSize=1, GetMinFeeMode mode=GetMinFeeMode::Block, unsigned int nBytes = 0) const;
bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=NULL)
bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=nullptr)
{
CAutoFile filein = CAutoFile(OpenBlockFile(pos.nFile, 0, pfileRet ? "rb+" : "rb"), SER_DISK, CLIENT_VERSION);
if (!filein)
@@ -681,10 +675,7 @@ public:
a.nLockTime == b.nLockTime);
}
friend bool operator!=(const CTransaction& a, const CTransaction& b)
{
return !(a == b);
}
friend bool operator!=(const CTransaction& a, const CTransaction& b) = default;
std::string ToStringShort() const
{
@@ -704,10 +695,10 @@ public:
vin.size(),
vout.size(),
nLockTime);
for (unsigned int i = 0; i < vin.size(); i++)
str += " " + vin[i].ToString() + "\n";
for (unsigned int i = 0; i < vout.size(); i++)
str += " " + vout[i].ToString() + "\n";
for (const CTxIn& txin : vin)
str += " " + txin.ToString() + "\n";
for (const CTxOut& txout : vout)
str += " " + txout.ToString() + "\n";
return str;
}
@@ -717,10 +708,10 @@ public:
}
bool ReadFromDisk(CTxDB& txdb, COutPoint prevout, CTxIndex& txindexRet);
bool ReadFromDisk(CTxDB& txdb, COutPoint prevout);
bool ReadFromDisk(CTxDBBase& txdb, COutPoint prevout, CTxIndex& txindexRet);
bool ReadFromDisk(CTxDBBase& txdb, COutPoint prevout);
bool ReadFromDisk(COutPoint prevout);
bool DisconnectInputs(CTxDB& txdb);
bool DisconnectInputs(CTxDBBase& txdb);
/** Fetch UTXO entries for all inputs from the UTXO database or mempool.
@@ -732,7 +723,7 @@ public:
@param[out] fInvalid returns true if transaction is invalid
@return Returns true if all inputs are found
*/
bool FetchInputs(CTxDB& txdb, const MapPrevTx& mapPendingUtxos,
bool FetchInputs(CTxDBBase& txdb, const MapPrevTx& mapPendingUtxos,
bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid);
/** Validate inputs against UTXO entries and verify signatures.
@@ -743,13 +734,13 @@ public:
@param[in] fMiner true if called from CreateNewBlock
@return Returns true if all checks succeed
*/
bool ConnectInputs(CTxDB& txdb, const MapPrevTx& inputs,
[[nodiscard]] bool ConnectInputs(CTxDBBase& txdb, const MapPrevTx& inputs,
const CBlockIndex* pindexBlock, bool fBlock, bool fMiner,
std::vector<CScriptCheck>* pvChecks = NULL);
std::vector<CScriptCheck>* pvChecks = nullptr);
bool ClientConnectInputs();
bool CheckTransaction() const;
bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL);
bool GetCoinAge(CTxDB& txdb, uint64_t& nCoinAge) const; // triangles: get transaction coin age
[[nodiscard]] bool CheckTransaction() const;
[[nodiscard]] bool AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs=true, bool* pfMissingInputs=nullptr);
bool GetCoinAge(CTxDBBase& txdb, uint64_t& nCoinAge) const; // triangles: get transaction coin age
protected:
const CTxOut& GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const;
@@ -801,7 +792,7 @@ public:
)
int SetMerkleBranch(const CBlock* pblock=NULL);
int SetMerkleBranch(const CBlock* pblock=nullptr);
// Return depth of transaction in blockchain:
// -1 : not in blockchain, and not in memory pool (conflicted transaction)
@@ -811,7 +802,7 @@ public:
int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
bool IsInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; }
int GetBlocksToMaturity() const;
bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true);
bool AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs=true);
bool AcceptToMemoryPool();
};
@@ -865,10 +856,7 @@ public:
a.vSpent == b.vSpent);
}
friend bool operator!=(const CTxIndex& a, const CTxIndex& b)
{
return !(a == b);
}
friend bool operator!=(const CTxIndex& a, const CTxIndex& b) = default;
int GetDepthInMainChain() const;
};
@@ -953,6 +941,7 @@ public:
vMerkleTree.clear();
nDoS = 0;
fCachedHash = false;
fMerkleTreeCached = false;
}
bool IsNull() const
@@ -962,6 +951,7 @@ public:
mutable uint256 cachedHash;
mutable bool fCachedHash;
mutable bool fMerkleTreeCached;
uint256 GetHash() const
{
@@ -1003,7 +993,7 @@ public:
std::pair<COutPoint, unsigned int> GetProofOfStake() const
{
return IsProofOfStake()? std::make_pair(vtx[1].vin[0].prevout, vtx[1].nTime) : std::make_pair(COutPoint(), (unsigned int)0);
return IsProofOfStake()? std::pair{vtx[1].vin[0].prevout, vtx[1].nTime} : std::pair{COutPoint(), (unsigned int)0};
}
// triangles: get max transaction timestamp
@@ -1017,6 +1007,9 @@ public:
uint256 BuildMerkleTree() const
{
if (fMerkleTreeCached)
return (vMerkleTree.empty() ? 0 : vMerkleTree.back());
vMerkleTree.clear();
for (const CTransaction& tx : vtx)
vMerkleTree.push_back(tx.GetHash());
@@ -1031,6 +1024,7 @@ public:
}
j += nSize;
}
fMerkleTreeCached = true;
return (vMerkleTree.empty() ? 0 : vMerkleTree.back());
}
@@ -1130,31 +1124,31 @@ public:
nTime, nBits, nNonce,
vtx.size(),
HexStr(vchBlockSig.begin(), vchBlockSig.end()).c_str());
for (unsigned int i = 0; i < vtx.size(); i++)
for (const CTransaction& tx : vtx)
{
printf(" ");
vtx[i].print();
tx.print();
}
printf(" vMerkleTree: ");
for (unsigned int i = 0; i < vMerkleTree.size(); i++)
printf("%s ", vMerkleTree[i].ToString().substr(0,10).c_str());
for (const uint256& merkle : vMerkleTree)
printf("%s ", merkle.ToString().substr(0,10).c_str());
printf("\n");
}
bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex);
bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck=false);
bool DisconnectBlock(CTxDBBase& txdb, CBlockIndex* pindex);
[[nodiscard]] bool ConnectBlock(CTxDBBase& txdb, CBlockIndex* pindex, bool fJustCheck=false);
bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true);
bool SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew);
bool SetBestChain(CTxDBBase& txdb, CBlockIndex* pindexNew);
bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const uint256& hashProofOfStake);
bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true, bool fCheckSig=true) const;
bool AcceptBlock();
[[nodiscard]] bool AcceptBlock();
bool GetCoinAge(uint64_t& nCoinAge) const; // triangles: calculate total coin age spent in block
bool SignBlock(CWallet& keystore, int64_t nFees);
bool CheckBlockSignature() const;
private:
bool SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew);
bool SetBestChainInner(CTxDBBase& txdb, CBlockIndex *pindexNew);
};
@@ -1208,9 +1202,9 @@ public:
CBlockIndex()
{
phashBlock = NULL;
pprev = NULL;
pnext = NULL;
phashBlock = nullptr;
pprev = nullptr;
pnext = nullptr;
nFile = 0;
nBlockPos = 0;
nHeight = 0;
@@ -1231,32 +1225,16 @@ public:
nNonce = 0;
}
CBlockIndex(unsigned int nFileIn, unsigned int nBlockPosIn, CBlock& block)
CBlockIndex(unsigned int nFileIn, unsigned int nBlockPosIn, CBlock& block) : CBlockIndex()
{
phashBlock = NULL;
pprev = NULL;
pnext = NULL;
nFile = nFileIn;
nBlockPos = nBlockPosIn;
nHeight = 0;
nChainTrust = 0;
nMint = 0;
nMoneySupply = 0;
nFlags = 0;
nStakeModifier = 0;
nStakeModifierChecksum = 0;
hashProofOfStake = 0;
if (block.IsProofOfStake())
{
SetProofOfStake();
prevoutStake = block.vtx[1].vin[0].prevout;
nStakeTime = block.vtx[1].nTime;
}
else
{
prevoutStake.SetNull();
nStakeTime = 0;
}
nVersion = block.nVersion;
hashMerkleRoot = block.hashMerkleRoot;
@@ -1309,9 +1287,9 @@ public:
int64_t GetMedianTimePast() const
{
int64_t pmedian[nMedianTimeSpan];
int64_t* pbegin = &pmedian[nMedianTimeSpan];
int64_t* pend = &pmedian[nMedianTimeSpan];
std::array<int64_t, nMedianTimeSpan> pmedian{};
auto pbegin = pmedian.end();
auto pend = pmedian.end();
const CBlockIndex* pindex = this;
for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev)
@@ -1537,10 +1515,7 @@ public:
Set((*mi).second);
}
CBlockLocator(const std::vector<uint256>& vHaveIn)
{
vHave = vHaveIn;
}
CBlockLocator(std::vector<uint256> vHaveIn) : vHave(std::move(vHaveIn)) {}
IMPLEMENT_SERIALIZE
(
@@ -1559,6 +1534,12 @@ public:
return vHave.empty();
}
// Return the first hash in the locator (peer's tip), or 0 if empty
uint256 GetTipHash() const
{
return vHave.empty() ? uint256(0) : vHave[0];
}
void Set(const CBlockIndex* pindex)
{
vHave.clear();
@@ -1652,7 +1633,7 @@ public:
std::map<uint256, CTransaction> mapTx;
std::map<COutPoint, CInPoint> mapNextTx;
bool accept(CTxDB& txdb, CTransaction &tx,
bool accept(CTxDBBase& txdb, CTransaction &tx,
bool fCheckInputs, bool* pfMissingInputs);
bool addUnchecked(const uint256& hash, CTransaction &tx);
bool remove(const CTransaction &tx, bool fRecursive = false);
@@ -1668,6 +1649,7 @@ public:
bool exists(uint256 hash)
{
LOCK(cs);
return (mapTx.count(hash) != 0);
}
@@ -1678,6 +1660,121 @@ public:
};
extern CTxMemPool mempool;
extern CScriptVerifyCache scriptVerifyCache;
/**
* Compact block relay for Tor-only networks.
*
* Instead of sending a full block, send the header + short transaction IDs.
* The receiver reconstructs the block from its mempool. For PoS blocks with
* 0-2 transactions (the common case), the coinstake is always prefilled, so
* the compact block IS the complete block no extra round-trip needed.
*/
/** Short transaction ID: first 6 bytes of SipHash(txid) */
static inline uint64_t GetShortTxId(const uint256& txhash, uint64_t nonce)
{
// Simple short ID: XOR txhash prefix with nonce
uint64_t id = 0;
memcpy(&id, txhash.begin(), 6); // first 6 bytes
id ^= nonce;
return id & 0xFFFFFFFFFFFFULL; // mask to 48 bits
}
class CCompactBlock
{
public:
// Block header fields
int nVersion;
uint256 hashPrevBlock;
uint256 hashMerkleRoot;
unsigned int nTime;
unsigned int nBits;
unsigned int nNonce;
std::vector<unsigned char> vchBlockSig;
// Compact block data
uint64_t nShortIdNonce; // nonce for short ID calculation
std::vector<uint64_t> vShortTxIds; // short IDs for non-prefilled txs
std::vector<std::pair<uint16_t, CTransaction>> vPrefilledTxn; // index + full tx
CCompactBlock() : nVersion(0), nTime(0), nBits(0), nNonce(0), nShortIdNonce(0) {}
// Construct from a full block: prefill coinbase + coinstake, short-ID the rest
CCompactBlock(const CBlock& block)
{
nVersion = block.nVersion;
hashPrevBlock = block.hashPrevBlock;
hashMerkleRoot = block.hashMerkleRoot;
nTime = block.nTime;
nBits = block.nBits;
nNonce = block.nNonce;
vchBlockSig = block.vchBlockSig;
nShortIdNonce = GetRand(std::numeric_limits<uint64_t>::max());
for (uint16_t i = 0; i < block.vtx.size(); i++)
{
if (i <= 1) {
// Always prefill coinbase (idx 0) and coinstake (idx 1)
vPrefilledTxn.push_back({i, block.vtx[i]});
} else {
vShortTxIds.push_back(GetShortTxId(block.vtx[i].GetHash(), nShortIdNonce));
}
}
}
IMPLEMENT_SERIALIZE
(
READWRITE(nVersion);
READWRITE(hashPrevBlock);
READWRITE(hashMerkleRoot);
READWRITE(nTime);
READWRITE(nBits);
READWRITE(nNonce);
READWRITE(vchBlockSig);
READWRITE(nShortIdNonce);
READWRITE(vShortTxIds);
READWRITE(vPrefilledTxn);
)
uint256 GetBlockHash() const
{
CBlock hdr;
hdr.nVersion = nVersion;
hdr.hashPrevBlock = hashPrevBlock;
hdr.hashMerkleRoot = hashMerkleRoot;
hdr.nTime = nTime;
hdr.nBits = nBits;
hdr.nNonce = nNonce;
return hdr.GetHash();
}
};
class CBlockTxnRequest
{
public:
uint256 blockhash;
std::vector<uint16_t> vIndex; // indices of missing transactions
IMPLEMENT_SERIALIZE
(
READWRITE(blockhash);
READWRITE(vIndex);
)
};
class CBlockTxnResponse
{
public:
uint256 blockhash;
std::vector<CTransaction> vTxn;
IMPLEMENT_SERIALIZE
(
READWRITE(blockhash);
READWRITE(vTxn);
)
};
/**
* Closure representing one script check for parallel verification.
@@ -1693,7 +1790,7 @@ private:
int nHashType;
public:
CScriptCheck() : ptxTo(NULL), nIn(0), nHashType(0) {}
CScriptCheck() : ptxTo(nullptr), nIn(0), nHashType(0) {}
CScriptCheck(const CScript& scriptPubKeyIn, const CScript& scriptSigIn,
const CTransaction& txToIn, unsigned int nInIn, int nHashTypeIn)
@@ -1702,7 +1799,12 @@ public:
bool operator()()
{
return ptxTo && VerifyScript(scriptSig, scriptPubKey, *ptxTo, nIn, nHashType);
if (!ptxTo)
return false;
if (!VerifyScript(scriptSig, scriptPubKey, *ptxTo, nIn, nHashType))
return false;
scriptVerifyCache.Set(ptxTo->GetHash(), nIn);
return true;
}
void swap(CScriptCheck& other)
@@ -1715,6 +1817,6 @@ public:
}
};
extern CCheckQueue<CScriptCheck>* pScriptCheckQueue;
extern std::unique_ptr<CCheckQueue<CScriptCheck>> pScriptCheckQueue;
#endif
+25 -14
View File
@@ -50,7 +50,7 @@ uint64_t nLastBlockSize = 0;
int64_t nLastCoinStakeSearchInterval = 0;
// We want to sort transactions by priority and fee, so:
typedef std::tuple<double, double, CTransaction*> TxPriority;
using TxPriority = std::tuple<double, double, CTransaction*>;
class TxPriorityCompare
{
bool byFee;
@@ -79,7 +79,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
// Create new block
unique_ptr<CBlock> pblock(new CBlock());
if (!pblock.get())
return NULL;
return nullptr;
CBlockIndex* pindexPrev = pindexBest;
@@ -136,7 +136,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
int64_t nFees = 0;
{
LOCK2(cs_main, mempool.cs);
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
// Priority order to process transactions
list<COrphan> vOrphan; // list memory doesn't move
@@ -145,13 +145,12 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
// This vector will be sorted into a priority queue:
vector<TxPriority> vecPriority;
vecPriority.reserve(mempool.mapTx.size());
for (map<uint256, CTransaction>::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi)
for (auto& [hash, tx] : mempool.mapTx)
{
CTransaction& tx = (*mi).second;
if (tx.IsCoinBase() || tx.IsCoinStake() || !tx.IsFinal())
continue;
COrphan* porphan = NULL;
COrphan* porphan = nullptr;
double dPriority = 0;
int64_t nTotalIn = 0;
bool fMissingInputs = false;
@@ -210,7 +209,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
porphan->dFeePerKb = dFeePerKb;
}
else
vecPriority.push_back(TxPriority(dPriority, dFeePerKb, &(*mi).second));
vecPriority.push_back(TxPriority(dPriority, dFeePerKb, &tx));
}
// Collect transactions into block
@@ -247,7 +246,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
continue;
// Transaction fee
int64_t nMinFee = tx.GetMinFee(nBlockSize, GMF_BLOCK);
int64_t nMinFee = tx.GetMinFee(nBlockSize, GetMinFeeMode::Block);
// Skip free transactions if we're past the minimum block size:
if (fSortedByFee && (dFeePerKb < nMinTxFee) && (nBlockSize + nTxSize >= nBlockMinSize))
@@ -372,7 +371,7 @@ bool CheckStake(CBlock* pblock, CWallet& wallet)
}
// Process this block the same as if we had received it from another node
if (!ProcessBlock(NULL, pblock))
if (!ProcessBlock(nullptr, pblock))
return error("CheckStake() : ProcessBlock, block not accepted");
}
@@ -387,6 +386,7 @@ void StakeMiner(CWallet *pwallet)
RenameThread("Triangles-miner");
bool fTryToSync = true;
bool fForceStaking = GetBoolArg("-forcestaking", false);
while (true)
{
@@ -401,7 +401,7 @@ void StakeMiner(CWallet *pwallet)
return;
}
while (vNodes.empty() || IsInitialBlockDownload())
while (!fForceStaking && (vNodes.empty() || IsInitialBlockDownload()))
{
nLastCoinStakeSearchInterval = 0;
fTryToSync = true;
@@ -410,10 +410,10 @@ void StakeMiner(CWallet *pwallet)
return;
}
if (fTryToSync)
if (fTryToSync && !fForceStaking)
{
fTryToSync = false;
if (vNodes.size() < 1 || nBestHeight < GetNumBlocksOfPeers())
if (vNodes.size() < 2 || nBestHeight < GetNumBlocksOfPeers())
{
MilliSleep(60000);
continue;
@@ -446,9 +446,20 @@ void StakeMiner(CWallet *pwallet)
{
printf("StakeMiner(): A proof-of-stake block has been found! %s\n", pblock->GetHash().ToString().c_str());
SetThreadPriority(THREAD_PRIORITY_NORMAL);
CheckStake(pblock.get(), *pwallet);
bool fAccepted = CheckStake(pblock.get(), *pwallet);
SetThreadPriority(THREAD_PRIORITY_LOWEST);
MilliSleep(500);
if (fAccepted)
{
MilliSleep(500);
}
else
{
// Block was orphaned or rejected — apply a cooldown to reduce
// fork oscillation. Without this, the staker immediately retries
// with a different timestamp, potentially creating competing forks.
printf("StakeMiner(): block not accepted, cooldown 30s\n");
MilliSleep(30000);
}
}
else
MilliSleep(500);
+207 -89
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"
@@ -37,7 +36,7 @@ extern "C" {
// int tor_main(int argc, char *argv[]);
}
static const int MAX_OUTBOUND_CONNECTIONS = 16;
static const int MAX_OUTBOUND_CONNECTIONS = 8; // reduced from 16 for Tor-only small networks
void ThreadMessageHandler2(void* parg);
void ThreadSocketHandler2(void* parg);
@@ -47,8 +46,8 @@ void ThreadOpenAddedConnections2(void* parg);
void ThreadMapPort2(void* parg);
#endif
void ThreadHTTPSeedFetch(void* parg);
void ThreadHTTPSeedFetch2(void* parg);
bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false);
bool ThreadHTTPSeedFetch2(void* parg);
bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false);
struct LocalServiceInfo {
@@ -60,7 +59,7 @@ struct LocalServiceInfo {
// Global state variables
//
bool fClient = false;
//bool fDiscover = true;
#ifdef USE_UPNP
bool fUseUPnP = GetBoolArg("-upnp", USE_UPNP);
@@ -72,10 +71,10 @@ static CCriticalSection cs_mapLocalHost;
static map<CNetAddr, LocalServiceInfo> mapLocalHost;
static bool vfReachable[NET_MAX] = {};
static bool vfLimited[NET_MAX] = {};
static CNode* pnodeLocalHost = NULL;
static CNode* pnodeLocalHost = nullptr;
CAddress addrSeenByPeer(CService("0.0.0.0", 0), nLocalServices);
uint64_t nLocalHostNonce = 0;
boost::array<int, THREAD_MAX> vnThreadsRunning;
std::array<int, THREAD_MAX> vnThreadsRunning;
static std::vector<SOCKET> vhListenSocket;
CAddrMan addrman;
@@ -92,7 +91,7 @@ CCriticalSection cs_vOneShots;
set<CNetAddr> setservAddNodeAddresses;
CCriticalSection cs_setservAddNodeAddresses;
static CSemaphore *semOutbound = NULL;
static CSemaphore *semOutbound = nullptr;
void AddOneShot(string strDest)
{
@@ -348,7 +347,7 @@ bool GetMyExternalIP2(const CService& addrConnect, const char* pszGet, const cha
closesocket(hSocket);
return false;
}
if (pszKeyword == NULL)
if (pszKeyword == nullptr)
break;
if (strLine.find(pszKeyword) != string::npos)
{
@@ -424,7 +423,7 @@ bool GetMyExternalIP(CNetAddr& ipRet)
"Connection: close\r\n"
"\r\n";
pszKeyword = NULL; // Returns just IP address
pszKeyword = nullptr; // Returns just IP address
}
if (GetMyExternalIP2(addrConnect, pszGet, pszKeyword, ipRet))
@@ -470,7 +469,7 @@ CNode* FindNode(const CNetAddr& ip)
if ((CNetAddr)pnode->addr == ip)
return (pnode);
}
return NULL;
return nullptr;
}
CNode* FindNode(std::string addrName)
@@ -479,7 +478,7 @@ CNode* FindNode(std::string addrName)
for (CNode* pnode : vNodes)
if (pnode->addrName == addrName)
return (pnode);
return NULL;
return nullptr;
}
CNode* FindNode(const CService& addr)
@@ -490,7 +489,7 @@ CNode* FindNode(const CService& addr)
if ((CService)pnode->addr == addr)
return (pnode);
}
return NULL;
return nullptr;
}
CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
@@ -500,12 +499,12 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
if (addrStr.find(".onion") == std::string::npos) {
if (fDebug)
printf("ConnectNode(): REJECTED non-onion address: %s (Tor-native mode)\n", addrStr.c_str());
return NULL;
return nullptr;
}
if (pszDest == NULL) {
if (pszDest == nullptr) {
if (IsLocal(addrConnect))
return NULL;
return nullptr;
// Look for an existing connection
CNode* pnode = FindNode((CService)addrConnect);
@@ -558,7 +557,7 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
}
else
{
return NULL;
return nullptr;
}
}
@@ -690,6 +689,9 @@ void CNode::copyStats(CNodeStats &stats)
X(fInbound);
X(nStartingHeight);
X(nMisbehavior);
X(nPingUsecTime);
X(nBlocksDelivered);
X(nAvgBlockLatencyUs);
}
#undef X
@@ -902,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;
}
}
}
@@ -1029,10 +1027,6 @@ void ThreadSocketHandler2(void* parg)
if (nErr != WSAEWOULDBLOCK)
printf("socket error accept failed: %d\n", nErr);
}
else if (nInbound >= GetArg("-maxconnections", 125) - MAX_OUTBOUND_CONNECTIONS)
{
closesocket(hSocket);
}
else if (CNode::IsBanned(addr))
{
printf("connection from %s dropped (banned)\n", addr.ToString().c_str());
@@ -1040,12 +1034,36 @@ void ThreadSocketHandler2(void* parg)
}
else
{
printf("accepted connection %s\n", addr.ToString().c_str());
CNode* pnode = new CNode(hSocket, addr, "", true);
pnode->AddRef();
{
LOCK(cs_vNodes);
vNodes.push_back(pnode);
int nMaxInbound = GetArg("-maxconnections", 125) - MAX_OUTBOUND_CONNECTIONS;
bool fAccept = (nInbound < nMaxInbound);
// Reserve 2 extra inbound slots for known seed nodes
if (!fAccept) {
bool fIsSeed = false;
static const char *(*strOnionSeedCheck)[1] = fTestNet ? strTestNetOnionSeed : strMainNetOnionSeed;
std::string incomingAddr = addr.ToStringIP();
for (unsigned int si = 0; strOnionSeedCheck[si][0] != nullptr; si++) {
if (incomingAddr.find(strOnionSeedCheck[si][0]) != std::string::npos) {
fIsSeed = true;
break;
}
}
if (fIsSeed && nInbound < nMaxInbound + 2) {
fAccept = true;
printf("accepted seed node %s (reserved slot)\n", addr.ToString().c_str());
}
}
if (fAccept) {
printf("accepted connection %s\n", addr.ToString().c_str());
CNode* pnode = new CNode(hSocket, addr, "", true);
pnode->AddRef();
{
LOCK(cs_vNodes);
vNodes.push_back(pnode);
}
} else {
closesocket(hSocket);
}
}
}
@@ -1137,14 +1155,14 @@ void ThreadSocketHandler2(void* parg)
printf("socket no message in first 60 seconds, %d %d\n", pnode->nLastRecv != 0, pnode->nLastSend != 0);
pnode->fDisconnect = true;
}
else if (GetTime() - pnode->nLastSend > 90*60 && GetTime() - pnode->nLastSendEmpty > 90*60)
else if (GetTime() - pnode->nLastSend > 10*60 && GetTime() - pnode->nLastSendEmpty > 10*60)
{
printf("socket not sending\n");
printf("socket not sending (10min timeout)\n");
pnode->fDisconnect = true;
}
else if (GetTime() - pnode->nLastRecv > 90*60)
else if (GetTime() - pnode->nLastRecv > 10*60)
{
printf("socket inactivity timeout\n");
printf("socket inactivity timeout (10min)\n");
pnode->fDisconnect = true;
}
}
@@ -1186,7 +1204,7 @@ void ThreadMapPort(void* parg)
PrintException(&e, "ThreadMapPort()");
} catch (...) {
vnThreadsRunning[THREAD_UPNP]--;
PrintException(NULL, "ThreadMapPort()");
PrintException(nullptr, "ThreadMapPort()");
}
printf("ThreadMapPort exited\n");
}
@@ -1307,7 +1325,7 @@ void MapPort()
printf("MapPort()...\n");
if (fUseUPnP && vnThreadsRunning[THREAD_UPNP] < 1)
{
if (!NewThread(ThreadMapPort, NULL))
if (!NewThread(ThreadMapPort, nullptr))
printf("Error: ThreadMapPort(ThreadMapPort) failed\n");
}
}
@@ -1381,11 +1399,11 @@ void ThreadOnionSeed(void* parg)
// Load hardcoded .onion seeds (if any)
// Load hardcoded .onion seeds and queue them for immediate direct connection
static const char *(*strOnionSeed)[1] = fTestNet ? strTestNetOnionSeed : strMainNetOnionSeed;
int found = 0;
for (unsigned int seed_idx = 0; strOnionSeed[seed_idx][0] != NULL; seed_idx++) {
for (unsigned int seed_idx = 0; strOnionSeed[seed_idx][0] != nullptr; seed_idx++) {
CNetAddr parsed;
if (!parsed.SetSpecial(strOnionSeed[seed_idx][0]))
throw runtime_error("ThreadOnionSeed() : invalid .onion seed");
@@ -1394,16 +1412,106 @@ void ThreadOnionSeed(void* parg)
CAddress addr = CAddress(CService(parsed, GetDefaultPort()));
addr.nTime = GetTime() - 3*nOneDay - GetRand(4*nOneDay);
addrman.Add(addr, parsed);
// Queue for immediate direct connection (OneShot) — don't wait for
// addrman selection which deprioritizes stale timestamps
std::string oneShotAddr = std::string(strOnionSeed[seed_idx][0])
+ ":" + std::to_string(GetDefaultPort());
AddOneShot(oneShotAddr);
found++;
}
printf("%d addresses from hardcoded .onion seeds\n", found);
printf("%d addresses from hardcoded .onion seeds (queued as OneShot)\n", found);
// Also fetch dynamic seeds from HTTP seed list
// Wait for Tor to establish circuits before attempting HTTPS seed fetch.
// The hardcoded OneShot connections can race ahead meanwhile.
printf("ThreadOnionSeed: waiting 20s for Tor circuits before HTTPS seed fetch...\n");
for (int i = 0; i < 20 && !fShutdown; i++)
MilliSleep(1000);
// Fetch dynamic seeds with retry — up to 4 attempts with increasing backoff.
// This is the primary discovery mechanism — seeds.cryptographic-triangles.org
ThreadHTTPSeedFetch2(NULL);
{
bool ok = false;
int delays[] = {0, 30, 60, 120};
for (int attempt = 0; attempt < 4 && !ok && !fShutdown; attempt++) {
if (attempt > 0) {
printf("ThreadOnionSeed: HTTPS seed fetch retry %d in %ds...\n", attempt, delays[attempt]);
for (int i = 0; i < delays[attempt] && !fShutdown; i++)
MilliSleep(1000);
}
if (!fShutdown)
ok = ThreadHTTPSeedFetch2(nullptr);
}
if (!ok && !fShutdown)
printf("ThreadOnionSeed: all HTTPS seed fetch attempts failed\n");
}
printf("ThreadOnionSeed: seeding complete\n");
printf("ThreadOnionSeed: initial seeding complete\n");
// Periodic re-seeding for isolated or under-connected nodes.
// EMERGENCY MODE: When 0 outbound peers, check every 15 seconds
// NORMAL MODE: Check every 2 minutes, re-seed when < 2 outbound peers
int64_t nLastReseed = GetTime();
bool bFirstReseed = true;
while (!fShutdown) {
// Count outbound peers to determine check interval
int nOutbound = 0;
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
if (!pnode->fInbound)
nOutbound++;
}
// Emergency mode: 0 peers = check every 15 seconds
// Low mode: 1 peer = check every 30 seconds
// Normal: 2+ peers = check every 2 minutes
int nSleepSeconds = (nOutbound == 0) ? 15 : (nOutbound < 2) ? 30 : 120;
for (int i = 0; i < nSleepSeconds && !fShutdown; i++)
MilliSleep(1000);
if (fShutdown) break;
// Recount after sleep
nOutbound = 0;
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
if (!pnode->fInbound)
nOutbound++;
}
// Emergency (0 peers): no cooldown, reseed immediately
// Low (1 peer): 60 second cooldown
// Normal (<2): 5 min first, 15 min subsequent
int64_t nCooldown;
if (nOutbound == 0)
nCooldown = 0; // immediate
else if (nOutbound < 2)
nCooldown = bFirstReseed ? 60 : 5 * 60;
else
nCooldown = bFirstReseed ? 5 * 60 : 15 * 60;
if (nOutbound < 2 && GetTime() - nLastReseed > nCooldown) {
if (nOutbound == 0)
printf("ThreadOnionSeed: EMERGENCY - 0 outbound peers, re-seeding immediately!\n");
else
printf("ThreadOnionSeed: low outbound peers (%d), re-seeding...\n", nOutbound);
ThreadHTTPSeedFetch2(nullptr);
// Re-queue hardcoded seeds for direct connection
for (unsigned int seed_idx = 0; strOnionSeed[seed_idx][0] != nullptr; seed_idx++) {
std::string oneShotAddr = std::string(strOnionSeed[seed_idx][0])
+ ":" + std::to_string(GetDefaultPort());
AddOneShot(oneShotAddr);
}
nLastReseed = GetTime();
bFirstReseed = false;
}
}
}
@@ -1461,7 +1569,7 @@ void ThreadDumpAddress(void* parg)
printf("ThreadDumpAddress exited\n");
}
void ThreadHTTPSeedFetch2(void* parg)
bool ThreadHTTPSeedFetch2(void* parg)
{
static const char* DEFAULT_SEED_URL_HOST = "seeds.cryptographic-triangles.org";
static const char* DEFAULT_SEED_URL_PATH = "/seeds.txt";
@@ -1479,8 +1587,8 @@ void ThreadHTTPSeedFetch2(void* parg)
printf("Fetching seed list from https://%s%s (via Tor)...\n", seedHost.c_str(), seedPath.c_str());
SSL_CTX* ctx = NULL;
SSL* ssl = NULL;
SSL_CTX* ctx = nullptr;
SSL* ssl = nullptr;
SOCKET hSocket = INVALID_SOCKET;
try {
@@ -1490,7 +1598,7 @@ void ThreadHTTPSeedFetch2(void* parg)
if (!ConnectSocketByName(addrResolved, hSocket, connectDest.c_str(), HTTPS_PORT, nConnectTimeout)) {
printf("HTTPS seed fetch: cannot connect to %s through Tor proxy\n", seedHost.c_str());
return;
return false;
}
// Set up TLS over the connected socket
@@ -1498,19 +1606,19 @@ void ThreadHTTPSeedFetch2(void* parg)
if (!ctx) {
printf("HTTPS seed fetch: SSL_CTX_new failed\n");
closesocket(hSocket);
return;
return false;
}
// Use system default CA certificates for verification
SSL_CTX_set_default_verify_paths(ctx);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);
ssl = SSL_new(ctx);
if (!ssl) {
printf("HTTPS seed fetch: SSL_new failed\n");
SSL_CTX_free(ctx);
closesocket(hSocket);
return;
return false;
}
// Set SNI hostname (required for Caddy/Let's Encrypt)
@@ -1527,7 +1635,7 @@ void ThreadHTTPSeedFetch2(void* parg)
SSL_free(ssl);
SSL_CTX_free(ctx);
closesocket(hSocket);
return;
return false;
}
printf("HTTPS seed fetch: TLS connection established to %s\n", seedHost.c_str());
@@ -1550,7 +1658,7 @@ void ThreadHTTPSeedFetch2(void* parg)
SSL_free(ssl);
SSL_CTX_free(ctx);
closesocket(hSocket);
return;
return false;
}
nSent += nBytes;
}
@@ -1569,27 +1677,27 @@ void ThreadHTTPSeedFetch2(void* parg)
SSL_free(ssl);
SSL_CTX_free(ctx);
closesocket(hSocket);
ssl = NULL;
ctx = NULL;
ssl = nullptr;
ctx = nullptr;
hSocket = INVALID_SOCKET;
if (response.empty()) {
printf("HTTPS seed fetch: empty response from %s\n", seedHost.c_str());
return;
return false;
}
// Parse HTTP response - find end of headers
size_t headerEnd = response.find("\r\n\r\n");
if (headerEnd == std::string::npos) {
printf("HTTPS seed fetch: malformed response (no header terminator)\n");
return;
return false;
}
// Check status code
std::string statusLine = response.substr(0, response.find("\r\n"));
if (statusLine.find("200") == std::string::npos) {
printf("HTTPS seed fetch: %s from %s\n", statusLine.c_str(), seedHost.c_str());
return;
return false;
}
std::string body = response.substr(headerEnd + 4);
@@ -1601,7 +1709,7 @@ void ThreadHTTPSeedFetch2(void* parg)
while (std::getline(lines, line))
{
if (fShutdown)
return;
return false;
// Trim whitespace and carriage returns
while (!line.empty() && (line.back() == '\r' || line.back() == ' ' || line.back() == '\t'))
@@ -1622,12 +1730,8 @@ void ThreadHTTPSeedFetch2(void* parg)
port = atoi(addrStr.substr(onionPos + 7).c_str());
addrStr = addrStr.substr(0, onionPos + 6); // keep ".onion"
} else if (addrStr.find(".onion") == std::string::npos) {
// Clearnet address - find last colon for port
size_t colonPos = addrStr.rfind(':');
if (colonPos != std::string::npos) {
port = atoi(addrStr.substr(colonPos + 1).c_str());
addrStr = addrStr.substr(0, colonPos);
}
// Tor-native: skip non-.onion addresses
continue;
}
if (port <= 0 || port > 65535)
@@ -1646,17 +1750,24 @@ void ThreadHTTPSeedFetch2(void* parg)
CAddress addr(CService(parsed, port));
addr.nTime = GetTime() - 3*24*60*60; // 3 days ago
addrman.Add(addr, CNetAddr("https-seed", true));
// Queue the first 8 seeds for immediate direct connection
if (found < 8) {
std::string oneShotAddr = addrStr + ":" + std::to_string(port);
AddOneShot(oneShotAddr);
}
found++;
}
}
printf("%d addresses found from HTTPS seed list (%s)\n", found, seedHost.c_str());
return found > 0;
} catch (std::exception& e) {
printf("HTTPS seed fetch failed: %s\n", e.what());
if (ssl) { SSL_shutdown(ssl); SSL_free(ssl); }
if (ctx) SSL_CTX_free(ctx);
if (hSocket != INVALID_SOCKET) closesocket(hSocket);
return false;
}
}
@@ -1674,7 +1785,7 @@ void ThreadHTTPSeedFetch(void* parg)
PrintException(&e, "ThreadHTTPSeedFetch()");
} catch (...) {
vnThreadsRunning[THREAD_HTTPSEED]--;
PrintException(NULL, "ThreadHTTPSeedFetch()");
PrintException(nullptr, "ThreadHTTPSeedFetch()");
}
printf("ThreadHTTPSeedFetch exited\n");
}
@@ -1695,7 +1806,7 @@ void ThreadOpenConnections(void* parg)
PrintException(&e, "ThreadOpenConnections()");
} catch (...) {
vnThreadsRunning[THREAD_OPENCONNECTIONS]--;
PrintException(NULL, "ThreadOpenConnections()");
PrintException(nullptr, "ThreadOpenConnections()");
}
printf("ThreadOpenConnections exited\n");
}
@@ -1769,7 +1880,7 @@ void ThreadOpenConnections2(void* parg)
for (string strAddr : mapMultiArgs["-connect"])
{
CAddress addr;
OpenNetworkConnection(addr, NULL, strAddr.c_str());
OpenNetworkConnection(addr, nullptr, strAddr.c_str());
for (int i = 0; i < 10 && i < nLoop; i++)
{
MilliSleep(500);
@@ -1877,7 +1988,7 @@ void ThreadOpenAddedConnections(void* parg)
PrintException(&e, "ThreadOpenAddedConnections()");
} catch (...) {
vnThreadsRunning[THREAD_ADDEDCONNECTIONS]--;
PrintException(NULL, "ThreadOpenAddedConnections()");
PrintException(nullptr, "ThreadOpenAddedConnections()");
}
printf("ThreadOpenAddedConnections exited\n");
}
@@ -2009,7 +2120,7 @@ void ThreadMessageHandler(void* parg)
PrintException(&e, "ThreadMessageHandler()");
} catch (...) {
vnThreadsRunning[THREAD_MESSAGEHANDLER]--;
PrintException(NULL, "ThreadMessageHandler()");
PrintException(nullptr, "ThreadMessageHandler()");
}
printf("ThreadMessageHandler exited\n");
}
@@ -2030,7 +2141,7 @@ void ThreadMessageHandler2(void* parg)
}
// Poll the connected nodes for messages
CNode* pnodeTrickle = NULL;
CNode* pnodeTrickle = nullptr;
if (!vNodesCopy.empty())
pnodeTrickle = vNodesCopy[GetRand(vNodesCopy.size())];
for (CNode* pnode : vNodesCopy)
@@ -2275,13 +2386,16 @@ void StartNode(void* parg)
// Make this thread recognisable as the startup thread
RenameThread("Triangles-start");
if (semOutbound == NULL) {
// initialize semaphore
int nMaxOutbound = min(MAX_OUTBOUND_CONNECTIONS, (int)GetArg("-maxconnections", 125));
if (semOutbound == nullptr) {
// initialize semaphore — use -maxoutbound if specified, else default
int nMaxOutbound = (int)GetArg("-maxoutbound", MAX_OUTBOUND_CONNECTIONS);
nMaxOutbound = min(nMaxOutbound, (int)GetArg("-maxconnections", 125));
nMaxOutbound = max(nMaxOutbound, 1); // at least 1 outbound
printf("Max outbound connections: %d\n", nMaxOutbound);
semOutbound = new CSemaphore(nMaxOutbound);
}
if (pnodeLocalHost == NULL)
if (pnodeLocalHost == nullptr)
pnodeLocalHost = new CNode(INVALID_SOCKET, CAddress(CService("127.0.0.1", 0), nLocalServices));
printf("StartNode(): pnodeLocalHost addr: %s\n",
@@ -2297,7 +2411,7 @@ void StartNode(void* parg)
if (!GetBoolArg("-onionseed", true))
printf(".onion seeding disabled\n");
else
if (!NewThread(ThreadOnionSeed, NULL))
if (!NewThread(ThreadOnionSeed, nullptr))
printf("Error: NewThread(ThreadOnionSeed) failed\n");
// Map ports with UPnP (default)
@@ -2310,34 +2424,34 @@ void StartNode(void* parg)
printf("HTTP seed fetch handled by onion seed thread\n");
else if (GetBoolArg("-noseedurl", false))
printf("HTTP seed fetch disabled\n");
else if (!NewThread(ThreadHTTPSeedFetch, NULL))
else if (!NewThread(ThreadHTTPSeedFetch, nullptr))
printf("Error: NewThread(ThreadHTTPSeedFetch) failed\n");
// Send and receive from sockets, accept connections
if (!NewThread(ThreadSocketHandler, NULL))
if (!NewThread(ThreadSocketHandler, nullptr))
printf("Error: NewThread(ThreadSocketHandler) failed\n");
// Initiate outbound connections from -addnode
if (!NewThread(ThreadOpenAddedConnections, NULL))
if (!NewThread(ThreadOpenAddedConnections, nullptr))
printf("Error: NewThread(ThreadOpenAddedConnections) failed\n");
// Initiate outbound connections
if (!NewThread(ThreadOpenConnections, NULL))
if (!NewThread(ThreadOpenConnections, nullptr))
printf("Error: NewThread(ThreadOpenConnections) failed\n");
// Process messages
if (!NewThread(ThreadMessageHandler, NULL))
if (!NewThread(ThreadMessageHandler, nullptr))
printf("Error: NewThread(ThreadMessageHandler) failed\n");
// Dump network addresses
if (!NewThread(ThreadDumpAddress, NULL))
if (!NewThread(ThreadDumpAddress, nullptr))
printf("Error; NewThread(ThreadDumpAddress) failed\n");
// Mine proof-of-stake blocks in the background
if (!GetBoolArg("-stake", true))
printf("Staking disabled at startup (stake=0).\n");
else
if (!NewThread(ThreadStakeMiner, pwalletMain))
if (!NewThread(ThreadStakeMiner, pwalletMain.get()))
printf("Error: NewThread(ThreadStakeMiner) failed\n");
}
@@ -2347,9 +2461,13 @@ bool StopNode()
fShutdown = true;
nTransactionsUpdated++;
int64_t nStart = GetTime();
if (semOutbound)
for (int i=0; i<MAX_OUTBOUND_CONNECTIONS; i++)
if (semOutbound) {
int nMaxOutbound = (int)GetArg("-maxoutbound", MAX_OUTBOUND_CONNECTIONS);
nMaxOutbound = min(nMaxOutbound, (int)GetArg("-maxconnections", 125));
nMaxOutbound = max(nMaxOutbound, 1);
for (int i=0; i<nMaxOutbound; i++)
semOutbound->post();
}
do
{
int nThreadsRunning = 0;
@@ -2449,8 +2567,8 @@ void RelayTransaction(const CTransaction& tx, const uint256& hash, const CDataSt
}
// Save original serialized message so newer versions are preserved
mapRelay.insert(std::make_pair(inv, ss));
vRelayExpiration.push_back(std::make_pair(GetTime() + 15 * 60, inv));
mapRelay.insert({inv, ss});
vRelayExpiration.push_back({GetTime() + 15 * 60, inv});
}
RelayInventory(inv);
+38 -205
View File
@@ -6,7 +6,7 @@
#define TRIANGLES_NET_H
#include <deque>
#include <boost/array.hpp>
#include <array>
#include <openssl/rand.h>
#ifndef WIN32
@@ -18,7 +18,6 @@
#include "protocol.h"
#include "addrman.h"
class CRequestTracker;
class CNode;
class CBlockIndex;
bool IsInitialBlockDownload();
@@ -26,7 +25,7 @@ extern int nBestHeight;
inline unsigned int ReceiveFloodSize() { return 100 * 1024 * 1024; } // 100 MB
inline unsigned int ReceiveFloodSize() { return 50 * 1024 * 1024; } // 50 MB (reduced for Tor-only network)
inline unsigned int SendBufferSize() { return 32 * 1024 * 1024; } // 32 MB
void AddOneShot(std::string strDest);
@@ -35,7 +34,7 @@ bool GetMyExternalIP(CNetAddr& ipRet);
void AddressCurrentlyConnected(const CService& addr);
CNode* FindNode(const CNetAddr& ip);
CNode* FindNode(const CService& ip);
CNode* ConnectNode(CAddress addrConnect, const char *strDest = NULL);
CNode* ConnectNode(CAddress addrConnect, const char *strDest = nullptr);
void MapPort();
unsigned short GetListenPort();
bool BindListenPort(const CService &bindAddr, std::string& strError=REF(std::string()));
@@ -64,10 +63,10 @@ bool AddLocal(const CService& addr, int nScore = LOCAL_NONE);
bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE);
bool SeenLocal(const CService& addr);
bool IsLocal(const CService& addr);
bool GetLocal(CService &addr, const CNetAddr *paddrPeer = NULL);
bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr);
bool IsReachable(const CNetAddr &addr);
void SetReachable(enum Network net, bool fFlag = true);
CAddress GetLocalAddress(const CNetAddr *paddrPeer = NULL);
CAddress GetLocalAddress(const CNetAddr *paddrPeer = nullptr);
enum
@@ -76,25 +75,6 @@ enum
MSG_BLOCK,
};
class CRequestTracker
{
public:
void (*fn)(void*, CDataStream&);
void* param1;
explicit CRequestTracker(void (*fnIn)(void*, CDataStream&)=NULL, void* param1In=NULL)
{
fn = fnIn;
param1 = param1In;
}
bool IsNull()
{
return fn == NULL;
}
};
/** Thread types */
enum threadId
{
@@ -118,7 +98,7 @@ extern bool fUseUPnP;
extern uint64_t nLocalServices;
extern uint64_t nLocalHostNonce;
extern CAddress addrSeenByPeer;
extern boost::array<int, THREAD_MAX> vnThreadsRunning;
extern std::array<int, THREAD_MAX> vnThreadsRunning;
extern CAddrMan addrman;
extern std::vector<CNode*> vNodes;
@@ -146,6 +126,9 @@ public:
bool fInbound;
int nStartingHeight;
int nMisbehavior;
int64_t nPingUsecTime;
int nBlocksDelivered;
int64_t nAvgBlockLatencyUs;
};
@@ -253,6 +236,7 @@ public:
bool fSuccessfullyConnected;
bool fDisconnect;
bool fPreferHeaders; // peer requested block announcements via headers (sendheaders)
bool fSendCmpct; // peer supports compact block relay (sendcmpct)
CSemaphoreGrant grantOutbound;
int nRefCount;
protected:
@@ -264,14 +248,24 @@ protected:
int nMisbehavior;
public:
std::map<uint256, CRequestTracker> mapRequests;
CCriticalSection cs_mapRequests;
uint256 hashContinue;
CBlockIndex* pindexLastGetBlocksBegin;
uint256 hashLastGetBlocksEnd;
CBlockIndex* pindexLastGetHeadersBegin;
uint256 hashLastGetHeadersEnd;
int nStartingHeight;
int64_t nLastTipCheck; // last time we asked this peer for chain tip
int64_t nLastIbdHeaderRequest; // last time we sent IBD-mode getheaders to this peer (heartbeat throttle)
int64_t nAvgBlockLatencyUs; // rolling average block delivery latency (microseconds)
int nBlocksDelivered; // count of blocks delivered by this peer
int nBestKnownHeight; // highest block height known to this peer (updated from inv/block msgs)
int nIncompatibleGetblocks; // count of getblocks with no common blocks (fork detection)
// BIP 31 ping/pong latency tracking
uint64_t nPingNonceSent; // nonce of last ping sent (0 = no outstanding ping)
int64_t nPingUsecStart; // microsecond timestamp when last ping was sent
int64_t nPingUsecTime; // last measured round-trip time (microseconds), 0 = unknown
int nPingRetryCount; // consecutive pings without pong response
// flood relay
std::vector<CAddress> vAddrToSend;
@@ -310,6 +304,7 @@ public:
fSuccessfullyConnected = false;
fDisconnect = false;
fPreferHeaders = false;
fSendCmpct = false;
nRefCount = 0;
nSendSize = 0;
nSendOffset = 0;
@@ -319,6 +314,16 @@ public:
pindexLastGetHeadersBegin = 0;
hashLastGetHeadersEnd = 0;
nStartingHeight = -1;
nLastTipCheck = 0;
nLastIbdHeaderRequest = 0;
nAvgBlockLatencyUs = 0;
nBlocksDelivered = 0;
nBestKnownHeight = -1;
nIncompatibleGetblocks = 0;
nPingNonceSent = 0;
nPingUsecStart = 0;
nPingUsecTime = 0;
nPingRetryCount = 0;
fGetAddr = false;
nMisbehavior = 0;
hashCheckpointKnown = 0;
@@ -439,7 +444,7 @@ public:
nRequestTime = nNow;
else
nRequestTime = std::max(nRequestTime + 2 * 60 * 1000000, nNow);
mapAskFor.insert(std::make_pair(nRequestTime, inv));
mapAskFor.insert({nRequestTime, inv});
}
@@ -518,13 +523,15 @@ public:
}
}
template<typename T1>
void PushMessage(const char* pszCommand, const T1& a1)
template<typename T1, typename... Args>
void PushMessage(const char* pszCommand, const T1& a1, const Args&... args)
{
try
{
BeginMessage(pszCommand);
ssSend << a1;
using swallow = int[];
(void)swallow{0, ((void)(ssSend << args), 0)...};
EndMessage();
}
catch (...)
@@ -534,180 +541,6 @@ public:
}
}
template<typename T1, typename T2>
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2)
{
try
{
BeginMessage(pszCommand);
ssSend << a1 << a2;
EndMessage();
}
catch (...)
{
AbortMessage();
throw;
}
}
template<typename T1, typename T2, typename T3>
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3)
{
try
{
BeginMessage(pszCommand);
ssSend << a1 << a2 << a3;
EndMessage();
}
catch (...)
{
AbortMessage();
throw;
}
}
template<typename T1, typename T2, typename T3, typename T4>
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4)
{
try
{
BeginMessage(pszCommand);
ssSend << a1 << a2 << a3 << a4;
EndMessage();
}
catch (...)
{
AbortMessage();
throw;
}
}
template<typename T1, typename T2, typename T3, typename T4, typename T5>
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5)
{
try
{
BeginMessage(pszCommand);
ssSend << a1 << a2 << a3 << a4 << a5;
EndMessage();
}
catch (...)
{
AbortMessage();
throw;
}
}
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5, const T6& a6)
{
try
{
BeginMessage(pszCommand);
ssSend << a1 << a2 << a3 << a4 << a5 << a6;
EndMessage();
}
catch (...)
{
AbortMessage();
throw;
}
}
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5, const T6& a6, const T7& a7)
{
try
{
BeginMessage(pszCommand);
ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7;
EndMessage();
}
catch (...)
{
AbortMessage();
throw;
}
}
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5, const T6& a6, const T7& a7, const T8& a8)
{
try
{
BeginMessage(pszCommand);
ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8;
EndMessage();
}
catch (...)
{
AbortMessage();
throw;
}
}
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9>
void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5, const T6& a6, const T7& a7, const T8& a8, const T9& a9)
{
try
{
BeginMessage(pszCommand);
ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9;
EndMessage();
}
catch (...)
{
AbortMessage();
throw;
}
}
void PushRequest(const char* pszCommand,
void (*fn)(void*, CDataStream&), void* param1)
{
uint256 hashReply;
RAND_bytes((unsigned char*)&hashReply, sizeof(hashReply));
{
LOCK(cs_mapRequests);
mapRequests[hashReply] = CRequestTracker(fn, param1);
}
PushMessage(pszCommand, hashReply);
}
template<typename T1>
void PushRequest(const char* pszCommand, const T1& a1,
void (*fn)(void*, CDataStream&), void* param1)
{
uint256 hashReply;
RAND_bytes((unsigned char*)&hashReply, sizeof(hashReply));
{
LOCK(cs_mapRequests);
mapRequests[hashReply] = CRequestTracker(fn, param1);
}
PushMessage(pszCommand, hashReply, a1);
}
template<typename T1, typename T2>
void PushRequest(const char* pszCommand, const T1& a1, const T2& a2,
void (*fn)(void*, CDataStream&), void* param1)
{
uint256 hashReply;
RAND_bytes((unsigned char*)&hashReply, sizeof(hashReply));
{
LOCK(cs_mapRequests);
mapRequests[hashReply] = CRequestTracker(fn, param1);
}
PushMessage(pszCommand, hashReply, a1, a2);
}
void PushGetBlocks(CBlockIndex* pindexBegin, uint256 hashEnd);
void PushGetHeaders(CBlockIndex* pindexBegin, uint256 hashEnd);
+11 -12
View File
@@ -13,7 +13,6 @@
#endif
#include "strlcpy.h"
#include <boost/algorithm/string/case_conv.hpp> // for to_lower()
using namespace std;
@@ -27,7 +26,7 @@ bool fNameLookup = false;
static const unsigned char pchIPv4[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff };
enum Network ParseNetwork(std::string net) {
boost::to_lower(net);
net = ToLower(net);
if (net == "ipv4") return NET_IPV4;
if (net == "ipv6") return NET_IPV6;
if (net == "tor") return NET_TOR;
@@ -42,7 +41,7 @@ void SplitHostPort(std::string in, int &portOut, std::string &hostOut) {
bool fBracketed = fHaveColon && (in[0]=='[' && in[colon-1]==']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe
bool fMultiColon = fHaveColon && (in.find_last_of(':',colon-1) != in.npos);
if (fHaveColon && (colon==0 || fBracketed || !fMultiColon)) {
char *endp = NULL;
char *endp = nullptr;
int n = strtol(in.c_str() + colon + 1, &endp, 10);
if (endp && *endp == 0 && n >= 0) {
in = in.substr(0, colon);
@@ -88,13 +87,13 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
# endif
aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST;
#endif
struct addrinfo *aiRes = NULL;
int nErr = getaddrinfo(pszName, NULL, &aiHint, &aiRes);
struct addrinfo *aiRes = nullptr;
int nErr = getaddrinfo(pszName, nullptr, &aiHint, &aiRes);
if (nErr)
return false;
struct addrinfo *aiTrav = aiRes;
while (aiTrav != NULL && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions))
while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions))
{
if (aiTrav->ai_family == AF_INET)
{
@@ -384,7 +383,7 @@ bool static ConnectSocketDirectly(const CService &addrConnect, SOCKET& hSocketRe
fd_set fdset;
FD_ZERO(&fdset);
FD_SET(hSocket, &fdset);
int nRet = select(hSocket + 1, NULL, &fdset, NULL, &timeout);
int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout);
if (nRet == 0)
{
printf("connection timeout\n");
@@ -454,7 +453,7 @@ bool SetProxy(enum Network net, CService addrProxy, int nSocksVersion) {
if (nSocksVersion != 0 && !addrProxy.IsValid())
return false;
LOCK(cs_proxyInfos);
proxyInfo[net] = std::make_pair(addrProxy, nSocksVersion);
proxyInfo[net] = {addrProxy, nSocksVersion};
return true;
}
@@ -473,7 +472,7 @@ bool SetNameProxy(CService addrProxy, int nSocksVersion) {
if (nSocksVersion != 0 && !addrProxy.IsValid())
return false;
LOCK(cs_proxyInfos);
nameproxyInfo = std::make_pair(addrProxy, nSocksVersion);
nameproxyInfo = {addrProxy, nSocksVersion};
return true;
}
@@ -868,7 +867,7 @@ std::string CNetAddr::ToStringIP() const
unsigned char sha3hash[32];
unsigned int sha3len = 0;
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(mdctx, EVP_sha3_256(), NULL);
EVP_DigestInit_ex(mdctx, EVP_sha3_256(), nullptr);
EVP_DigestUpdate(mdctx, checksumInput, 48);
EVP_DigestFinal_ex(mdctx, sha3hash, &sha3len);
EVP_MD_CTX_free(mdctx);
@@ -890,7 +889,7 @@ std::string CNetAddr::ToStringIP() const
socklen_t socklen = sizeof(sockaddr);
if (serv.GetSockAddr((struct sockaddr*)&sockaddr, &socklen)) {
char name[1025] = "";
if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name, sizeof(name), NULL, 0, NI_NUMERICHOST))
if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name, sizeof(name), nullptr, 0, NI_NUMERICHOST))
return std::string(name);
}
if (IsIPv4())
@@ -1043,7 +1042,7 @@ static const int NET_UNKNOWN = NET_MAX + 0;
static const int NET_TEREDO = NET_MAX + 1;
int static GetExtNetwork(const CNetAddr *addr)
{
if (addr == NULL)
if (addr == nullptr)
return NET_UNKNOWN;
if (addr->IsRFC4380())
return NET_TEREDO;
+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;
}
};
+16 -8
View File
@@ -1,20 +1,28 @@
#ifndef TRIANGLES_ONIONSEED_H
#define TRIANGLES_ONIONSEED_H
// Hardcoded onion seed nodes for initial peer discovery.
// Also fetched dynamically via https://seeds.cryptographic-triangles.org/seeds.txt
static const char *strMainNetOnionSeed[][1] = {
{"jbpfhe7zw3qm67wy3j2ayysp3mnrjobopthnko3b3sgahqtecblwqmid.onion"},
{"uddaxjbo3lh2zskg7w6gwln4ty5cel7q4c5jbx7fdtv6zf2j47gdlyad.onion"},
{"el5sirhhleecuctpeeprelzubpqmoqivvra3rzlwbjttinxa4fq3wnid.onion"},
{"sj5dhybnlp3v4y5niyc5unrnd6s43lyx5ibup7rolyosjbi2u2hsbvyd.onion"},
{"i3kr5meha7se4ns3wss3h7v46m6uksfzv4wrohdqxpj6n35wyo2bvlid.onion"},
{NULL}
// DNS2 - primary bootstrap server (194.233.88.206)
{"gxvrhv3qitnc6kobrhsrse46bmcfitnybapor3or3oczzuxn6hfzxyid.onion"},
// DNS3 - canonical chain reference (74.208.167.19)
{"i6tk7soznftvoibtskwlezviskiererhjndpsmrff4kaxw7jnd5izfqd.onion"},
// Hetzner Helsinki - ARM64 staking node (46.62.249.20)
{"nawqqoazk2hhaglygulpeg6kh7hsgnvi2fursdvpvkantu4ojj26taid.onion"},
// Contabo seed 1 (173.212.201.200)
{"vmepp7plxngv4qpyngbgtb6njwnmlwy4api64xnwkhaf6fm3qlqtpfad.onion"},
// Contabo seed 2
{"nsldmfujkiwsfha42ajp5zx7gz3ekwdk4nvowdpf56mayuxnzshuykqd.onion"},
// Contabo seed 3
{"on4noksywc7b6cdbbxsp535l7j4cugunvlyz3iyhf6sfcg2qzaoy3eqd.onion"},
// Contabo seed 4
{"3uyzltm5cy7xzunncp3d7ariw75erabdnj4l3cxwvsxb6h4orc7eiqad.onion"},
{nullptr}
};
static const char *strTestNetOnionSeed[][1] = {
{NULL}
{nullptr}
};
#endif
+2 -1
View File
@@ -68,7 +68,8 @@ class CMessageHeader
/** nServices flags */
enum
{
NODE_NETWORK = (1 << 0),
NODE_NETWORK = (1 << 0),
NODE_SNAPSHOT = (1 << 1), // peer can serve UTXO snapshot chunks
};
/** A CService with information about it as peer */
+5 -37
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
+1 -1
View File
@@ -545,7 +545,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
int64_t nFee = nTransactionFee * (1 + (int64_t)nBytes / 1000);
// Min Fee
int64_t nMinFee = txDummy.GetMinFee(1, GMF_SEND, nBytes);
int64_t nMinFee = txDummy.GetMinFee(1, GetMinFeeMode::Send, nBytes);
nPayFee = max(nFee, nMinFee);
+1 -1
View File
@@ -868,7 +868,7 @@ QWidget#line {
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;a href=&quot;http://explorer.triangles.technology&quot;&gt; &amp;#187; TRI block explorer&lt;/a&gt;&lt;/body&gt;</string>
&lt;a href=&quot;https://blocks.cryptographic-triangles.org&quot;&gt; &amp;#187; TRI block explorer&lt;/a&gt;&lt;/body&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+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;
+142
View File
@@ -0,0 +1,142 @@
// Copyright (c) 2026 The Triangles developers
// Distributed under the MIT/X11 software license.
#include "hdseeddialog.h"
#include "walletmodel.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QLabel>
#include <QMessageBox>
#include <QFont>
HDSeedDialog::HDSeedDialog(QWidget *parent)
: QDialog(parent), model(0), seedText(0), statusLabel(0)
{
setWindowTitle(tr("HD Seed Phrase (BIP39)"));
resize(560, 360);
QVBoxLayout *layout = new QVBoxLayout(this);
QLabel *intro = new QLabel(tr(
"A 24-word seed phrase is a complete backup of this wallet. Anyone who has it "
"can spend your coins. Write it down on paper and keep it offline."), this);
intro->setWordWrap(true);
layout->addWidget(intro);
seedText = new QPlainTextEdit(this);
seedText->setPlaceholderText(tr(
"Your 24-word phrase appears here when you generate or reveal it. "
"To restore, paste an existing 24-word phrase here and click 'Restore from Phrase'."));
QFont mono("monospace");
mono.setStyleHint(QFont::Monospace);
seedText->setFont(mono);
layout->addWidget(seedText);
statusLabel = new QLabel(this);
statusLabel->setWordWrap(true);
layout->addWidget(statusLabel);
QHBoxLayout *btns = new QHBoxLayout();
QPushButton *genBtn = new QPushButton(tr("Generate New"), this);
QPushButton *showBtn = new QPushButton(tr("Reveal for Backup"), this);
QPushButton *restoreBtn = new QPushButton(tr("Restore from Phrase"), this);
QPushButton *closeBtn = new QPushButton(tr("Close"), this);
btns->addWidget(genBtn);
btns->addWidget(showBtn);
btns->addWidget(restoreBtn);
btns->addStretch();
btns->addWidget(closeBtn);
layout->addLayout(btns);
connect(genBtn, SIGNAL(clicked()), this, SLOT(onGenerate()));
connect(showBtn, SIGNAL(clicked()), this, SLOT(onShow()));
connect(restoreBtn, SIGNAL(clicked()), this, SLOT(onRestore()));
connect(closeBtn, SIGNAL(clicked()), this, SLOT(accept()));
}
void HDSeedDialog::setModel(WalletModel *modelIn)
{
model = modelIn;
refreshStatus();
}
void HDSeedDialog::refreshStatus()
{
if (!model || !statusLabel) return;
if (model->hdEnabled())
statusLabel->setText(tr("Status: HD seed is ACTIVE. Use 'Reveal for Backup' to view your phrase."));
else
statusLabel->setText(tr("Status: no HD seed yet. Use 'Generate New' to create one."));
}
void HDSeedDialog::onGenerate()
{
if (!model) return;
if (model->hdEnabled()) {
QMessageBox::warning(this, tr("HD seed already set"),
tr("This wallet already has an HD seed. Use 'Reveal for Backup' to view it."));
return;
}
if (QMessageBox::question(this, tr("Generate new seed"),
tr("Generate a new 24-word HD seed for this wallet?"),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
WalletModel::UnlockContext ctx(model->requestUnlock());
if (!ctx.isValid()) return;
QString mnemonic, err;
if (!model->hdNew(mnemonic, err)) {
QMessageBox::critical(this, tr("Error"), err);
return;
}
seedText->setPlainText(mnemonic);
QMessageBox::information(this, tr("Write this down"),
tr("Your new 24-word seed phrase is shown above. Write it on paper and store it safely "
"and offline. This is the only backup of this wallet."));
refreshStatus();
}
void HDSeedDialog::onShow()
{
if (!model) return;
WalletModel::UnlockContext ctx(model->requestUnlock());
if (!ctx.isValid()) return;
QString mnemonic, err;
if (!model->hdShow(mnemonic, err)) {
QMessageBox::critical(this, tr("Error"), err);
return;
}
seedText->setPlainText(mnemonic);
}
void HDSeedDialog::onRestore()
{
if (!model) return;
QString phrase = seedText->toPlainText().trimmed();
if (phrase.isEmpty()) {
QMessageBox::warning(this, tr("No phrase"),
tr("Paste a 24-word phrase into the box first."));
return;
}
if (QMessageBox::question(this, tr("Restore from phrase"),
tr("Restore the HD seed from the phrase in the box and rescan the chain? "
"This replaces the current HD seed."),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
WalletModel::UnlockContext ctx(model->requestUnlock());
if (!ctx.isValid()) return;
QString err;
if (!model->hdRestore(phrase, err)) {
QMessageBox::critical(this, tr("Error"), err);
return;
}
QMessageBox::information(this, tr("Restored"),
tr("HD seed restored and the chain was rescanned for your funds."));
refreshStatus();
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) 2026 The Triangles developers
// Distributed under the MIT/X11 software license.
#ifndef HDSEEDDIALOG_H
#define HDSEEDDIALOG_H
#include <QDialog>
class WalletModel;
QT_BEGIN_NAMESPACE
class QPlainTextEdit;
class QLabel;
QT_END_NAMESPACE
/** Generate, reveal (for backup), and restore the wallet's BIP39 HD seed phrase. */
class HDSeedDialog : public QDialog
{
Q_OBJECT
public:
explicit HDSeedDialog(QWidget *parent = 0);
void setModel(WalletModel *model);
private:
WalletModel *model;
QPlainTextEdit *seedText;
QLabel *statusLabel;
void refreshStatus();
private slots:
void onGenerate();
void onShow();
void onRestore();
};
#endif // HDSEEDDIALOG_H
+10 -16
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
@@ -292,12 +292,6 @@ bool IntroDialog::pickDataDirectory()
};
bool success = Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, strError);
if (!success) {
host = Bootstrap::FALLBACK_HOST;
progress.setValue(0);
success = Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, strError);
}
if (!success) {
QMessageBox::warning(0, "Triangles",
QString("Could not download blockchain snapshot:\n%1\n\n"
@@ -311,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();
@@ -328,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
+4 -2
View File
@@ -13,7 +13,6 @@
#include "ui_interface.h"
#include "util.h"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/interprocess/ipc/message_queue.hpp>
#include <boost/version.hpp>
@@ -26,6 +25,9 @@ using namespace boost;
using namespace boost::interprocess;
using namespace boost::posix_time;
#include <algorithm>
#include <cctype>
#if defined MAC_OSX || defined __FreeBSD__
// URI handling not implemented on OSX yet
@@ -42,7 +44,7 @@ static bool ipcScanCmd(int argc, char *argv[], bool fRelay)
bool fSent = false;
for (int i = 1; i < argc; i++)
{
if (boost::algorithm::istarts_with(argv[i], "Triangles:"))
if (std::equal(std::begin("Triangles:"), std::end("Triangles:") - 1, argv[i], [](char a, char b) { return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b)); }))
{
const char *strURI = argv[i];
try {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 795 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 17 KiB

+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>";
+2 -2
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)
@@ -229,7 +229,7 @@ int main(int argc, char *argv[])
// calling Shutdown().
ClientModel clientModel(&optionsModel);
WalletModel walletModel(pwalletMain, &optionsModel);
WalletModel walletModel(pwalletMain.get(), &optionsModel);
window.setClientModel(&clientModel);
window.setWalletModel(&walletModel);
+22 -2
View File
@@ -31,6 +31,7 @@
#include "trianglesunits.h"
#include "guiconstants.h"
#include "askpassphrasedialog.h"
#include "hdseeddialog.h"
#include "notificator.h"
#include "guiutil.h"
#include "rpcconsole.h"
@@ -89,7 +90,8 @@
#include <iostream>
extern CWallet* pwalletMain;
#include <memory>
extern std::unique_ptr<CWallet> pwalletMain;
extern int64_t nLastCoinStakeSearchInterval;
extern unsigned int nTargetSpacing;
double GetPoSKernelPS();
@@ -483,6 +485,8 @@ void TrianglesGUI::createActions(bool fIsTestnet)
backupWalletAction->setStatusTip(tr("Backup wallet to another location"));
changePassphraseAction = new QAction(QIcon(":/menu_16/passphrase"), tr("&Change Passphrase..."), this);
changePassphraseAction->setStatusTip(tr("Change the passphrase used for wallet encryption"));
hdSeedAction = new QAction(QIcon(":/menu_16/passphrase"), tr("&Seed Phrase (HD Backup)..."), this);
hdSeedAction->setStatusTip(tr("Generate, restore, or back up your 24-word HD seed phrase"));
unlockWalletAction = new QAction(QIcon(":/menu_16/unlock"), tr("&Unlock Wallet..."), this);
unlockWalletAction->setStatusTip(tr("Unlock wallet"));
unlockWalletStakingAction = new QAction(QIcon(":/menu_16/unlock"), tr("&Unlock Wallet for staking..."), this);
@@ -508,6 +512,7 @@ void TrianglesGUI::createActions(bool fIsTestnet)
connect(encryptWalletAction, SIGNAL(triggered(bool)), this, SLOT(encryptWallet(bool)));
connect(backupWalletAction, SIGNAL(triggered()), this, SLOT(backupWallet()));
connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase()));
connect(hdSeedAction, SIGNAL(triggered()), this, SLOT(hdSeedManager()));
connect(unlockWalletAction, SIGNAL(triggered()), this, SLOT(unlockWallet()));
connect(unlockWalletStakingAction, SIGNAL(triggered()), this, SLOT(unlockWalletStaking()));
connect(lockWalletAction, SIGNAL(triggered()), this, SLOT(lockWallet()));
@@ -537,6 +542,7 @@ void TrianglesGUI::createMenuBar()
QMenu *settings = appMenuBar->addMenu(tr("&Settings"));
settings->addAction(encryptWalletAction);
settings->addAction(changePassphraseAction);
settings->addAction(hdSeedAction);
settings->addAction(unlockWalletAction);
settings->addAction(lockWalletAction);
settings->addSeparator();
@@ -655,7 +661,7 @@ void TrianglesGUI::ensureMessageModel()
if(messageModel || !walletModel)
return;
setMessageModel(new MessageModel(pwalletMain, walletModel, this));
setMessageModel(new MessageModel(pwalletMain.get(), walletModel, this));
}
void TrianglesGUI::ensureSendCoinsPage()
@@ -1447,6 +1453,7 @@ void TrianglesGUI::menuOperationsRequested()
QAction* unlockWalletStaking = menu.addAction(QIcon(":/menu_16/unlock"), tr("&Unlock Wallet...").remove('&').remove("..."));
QAction* lockWallet = menu.addAction(QIcon(":/menu_16/lock"), tr("&Lock Wallet...").remove('&').remove("..."));
QAction* changePassword = menu.addAction(QIcon(":/menu_16/passphrase"), tr("&Change Passphrase...").remove('&').remove("..."));
QAction* hdSeed = menu.addAction(QIcon(":/menu_16/passphrase"), tr("Seed Phrase (HD Backup)..."));
QAction* signMessage = menu.addAction(QIcon(":/menu_16/sign"), tr("Sign &message...").remove('&').remove("..."));
QAction* verifySignature = menu.addAction(QIcon(":/menu_16/verify"), tr("&Verify message...").remove('&').remove("..."));
@@ -1507,6 +1514,10 @@ void TrianglesGUI::menuOperationsRequested()
if (walletModel->getEncryptionStatus() == WalletModel::Unlocked || walletModel->getEncryptionStatus() == WalletModel::Locked)
changePassphrase();
}
else if (selected == hdSeed)
{
hdSeedManager();
}
else if (selected == signMessage)
{
gotoSignMessageTab();
@@ -1613,6 +1624,15 @@ void TrianglesGUI::changePassphrase()
dlg.exec();
}
void TrianglesGUI::hdSeedManager()
{
if (!walletModel)
return;
HDSeedDialog dlg(this);
dlg.setModel(walletModel);
dlg.exec();
}
void TrianglesGUI::unlockWalletStaking()
{
+3
View File
@@ -131,6 +131,7 @@ private:
QAction *encryptWalletAction;
QAction *backupWalletAction;
QAction *changePassphraseAction;
QAction *hdSeedAction;
QAction *unlockWalletAction;
QAction *unlockWalletStakingAction;
QAction *lockWalletAction;
@@ -243,6 +244,8 @@ private slots:
void backupWallet();
/** Change encrypted wallet passphrase */
void changePassphrase();
/** Open the HD seed phrase (generate/restore/backup) dialog */
void hdSeedManager();
/** Ask for passphrase to unlock wallet temporarily */
void unlockWallet();
/** Ask for passphrase to unlock wallet temporarily - FOR STAKING ONLY */
+58 -9
View File
@@ -389,7 +389,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie
for (it = mapStealthNarr.begin(); it != mapStealthNarr.end(); ++it)
{
char key[64];
if (snprintf(key, sizeof(key), "n_%u") < 1)
if (snprintf(key, sizeof(key), "n_%u", it->first) < 1)
{
printf("CreateStealthTransaction(): Error creating narration key.");
continue;
@@ -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
@@ -673,3 +676,49 @@ void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts)
{
return;
}
// ---- HD wallet (BIP39/BIP32) ----
bool WalletModel::hdEnabled() const
{
return wallet->IsHDEnabled();
}
bool WalletModel::hdNew(QString &mnemonicOut, QString &errorOut)
{
std::string mnemonic, strError;
if (!wallet->SetHDSeed("", "", true, mnemonic, strError)) {
errorOut = QString::fromStdString(strError);
return false;
}
wallet->TopUpKeyPool();
mnemonicOut = QString::fromStdString(mnemonic);
return true;
}
bool WalletModel::hdRestore(const QString &mnemonic, QString &errorOut)
{
std::string out, strError;
if (!wallet->SetHDSeed(mnemonic.toStdString(), "", false, out, strError)) {
errorOut = QString::fromStdString(strError);
return false;
}
wallet->TopUpKeyPool();
{
LOCK2(cs_main, wallet->cs_wallet);
wallet->ScanForWalletTransactions(pindexGenesisBlock, true);
wallet->ReacceptWalletTransactions();
}
return true;
}
bool WalletModel::hdShow(QString &mnemonicOut, QString &errorOut)
{
std::string mnemonic;
if (!wallet->GetHDMnemonic(mnemonic)) {
errorOut = QObject::tr("Wallet has no HD seed (use 'Generate New').");
return false;
}
mnemonicOut = QString::fromStdString(mnemonic);
return true;
}
+10 -1
View File
@@ -8,6 +8,7 @@
#include <QMutex>
#include "allocators.h" /* for SecureString */
#include "../util_signal.h"
class OptionsModel;
class AddressTableModel;
@@ -92,7 +93,7 @@ public:
};
// Send coins to a list of recipients
SendCoinsReturn sendCoins(const QList<SendCoinsRecipient> &recipients, const CCoinControl *coinControl=NULL);
SendCoinsReturn sendCoins(const QList<SendCoinsRecipient> &recipients, const CCoinControl *coinControl=nullptr);
// Wallet encryption
bool setWalletEncrypted(bool encrypted, const SecureString &passphrase);
@@ -102,6 +103,12 @@ public:
// Wallet backup
bool backupWallet(const QString &filename);
// ---- HD wallet (BIP39/BIP32) ----
bool hdEnabled() const;
bool hdNew(QString &mnemonicOut, QString &errorOut);
bool hdRestore(const QString &mnemonic, QString &errorOut);
bool hdShow(QString &mnemonicOut, QString &errorOut);
// RAI object for unlocking wallet, returned by requestUnlock()
class UnlockContext
{
@@ -157,6 +164,8 @@ private:
void unsubscribeFromCoreSignals();
bool checkBalanceChanged();
CSignalConnections m_core_signal_connections;
public slots:
/* Wallet status might have changed */
+7 -10
View File
@@ -12,8 +12,6 @@
#include "wallet.h"
#include "init.h"
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace json_spirit;
@@ -95,7 +93,7 @@ bool CheckRESTRateLimit(const string& strIP)
string HTTPReplyREST(int nStatus, const string& strMsg, const string& contentType)
{
string strCorsOrigin = GetArg("-restcorsorigin", "*");
string strCorsOrigin = GetArg(std::string_view{"-restcorsorigin"}, std::string_view{"*"});
const char *cStatus;
if (nStatus == 200) cStatus = "OK";
@@ -149,12 +147,11 @@ static void ParseRESTPath(const string& strURI, vector<string>& parts, map<strin
}
// Split path into parts
boost::split(parts, path, boost::is_any_of("/"));
parts = SplitString(path, '/');
// Parse query parameters
if (!queryString.empty()) {
vector<string> pairs;
boost::split(pairs, queryString, boost::is_any_of("&"));
auto pairs = SplitString(queryString, '&');
for (size_t i = 0; i < pairs.size(); i++) {
size_t eq = pairs[i].find('=');
if (eq != string::npos)
@@ -175,12 +172,12 @@ bool IsRESTPath(const string& strURI)
static bool RESTAuthorized(map<string, string>& mapHeaders)
{
// Check Bearer token first (if -restapikey is set)
string strApiKey = GetArg("-restapikey", "");
string strApiKey = GetArg(std::string_view{"-restapikey"}, std::string_view{""});
if (!strApiKey.empty()) {
string strAuth = mapHeaders.count("authorization") ? mapHeaders["authorization"] : "";
if (strAuth.substr(0, 7) == "Bearer ") {
string strToken = strAuth.substr(7);
boost::trim(strToken);
strToken = TrimString(strToken);
if (TimingResistantEqual(strToken, strApiKey))
return true;
}
@@ -300,8 +297,8 @@ static bool HandleBlockHeader(const string& param, string& strReply, int& nStatu
result.push_back(Pair("height", pblockindex->nHeight));
result.push_back(Pair("version", pblockindex->nVersion));
result.push_back(Pair("merkleroot", pblockindex->hashMerkleRoot.GetHex()));
result.push_back(Pair("time", (boost::int64_t)pblockindex->GetBlockTime()));
result.push_back(Pair("nonce", (boost::uint64_t)pblockindex->nNonce));
result.push_back(Pair("time", (int64_t)pblockindex->GetBlockTime()));
result.push_back(Pair("nonce", (uint64_t)pblockindex->nNonce));
result.push_back(Pair("bits", HexBits(pblockindex->nBits)));
result.push_back(Pair("difficulty", GetDifficulty(pblockindex)));
result.push_back(Pair("flags", strprintf("%s%s",
+560 -11
View File
@@ -9,6 +9,20 @@
#include "addressindex.h"
#include "txdb.h"
#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;
@@ -20,14 +34,17 @@ double GetDifficulty(const CBlockIndex* blockindex)
{
// Floating point number that is a multiple of the minimum difficulty,
// minimum difficulty = 1.0.
if (blockindex == NULL)
if (blockindex == nullptr)
{
if (pindexBest == NULL)
if (pindexBest == nullptr)
return 1.0;
else
blockindex = GetLastBlockIndex(pindexBest, false);
}
if (blockindex == nullptr)
return 1.0;
int nShift = (blockindex->nBits >> 24) & 0xff;
double dDiff =
@@ -81,7 +98,7 @@ double GetPoSKernelPS()
int nStakesHandled = 0, nStakesTime = 0;
CBlockIndex* pindex = pindexBest;;
CBlockIndex* pindexPrevStake = NULL;
CBlockIndex* pindexPrevStake = nullptr;
while (pindex && nStakesHandled < nPoSInterval)
{
@@ -111,8 +128,8 @@ Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool fPri
result.push_back(Pair("version", block.nVersion));
result.push_back(Pair("merkleroot", block.hashMerkleRoot.GetHex()));
result.push_back(Pair("mint", ValueFromAmount(blockindex->nMint)));
result.push_back(Pair("time", (boost::int64_t)block.GetBlockTime()));
result.push_back(Pair("nonce", (boost::uint64_t)block.nNonce));
result.push_back(Pair("time", (int64_t)block.GetBlockTime()));
result.push_back(Pair("nonce", (uint64_t)block.nNonce));
result.push_back(Pair("bits", HexBits(block.nBits)));
result.push_back(Pair("difficulty", GetDifficulty(blockindex)));
result.push_back(Pair("blocktrust", leftTrim(blockindex->GetBlockTrust().GetHex(), '0')));
@@ -313,8 +330,8 @@ Value getblockheader(const Array& params, bool fHelp)
result.push_back(Pair("version", pblockindex->nVersion));
result.push_back(Pair("merkleroot", pblockindex->hashMerkleRoot.GetHex()));
result.push_back(Pair("mint", ValueFromAmount(pblockindex->nMint)));
result.push_back(Pair("time", (boost::int64_t)pblockindex->GetBlockTime()));
result.push_back(Pair("nonce", (boost::uint64_t)pblockindex->nNonce));
result.push_back(Pair("time", (int64_t)pblockindex->GetBlockTime()));
result.push_back(Pair("nonce", (uint64_t)pblockindex->nNonce));
result.push_back(Pair("bits", HexBits(pblockindex->nBits)));
result.push_back(Pair("difficulty", GetDifficulty(pblockindex)));
result.push_back(Pair("blocktrust", leftTrim(pblockindex->GetBlockTrust().GetHex(), '0')));
@@ -360,6 +377,287 @@ Value gettxoutsetinfo(const Array& params, bool fHelp)
return obj;
}
static void GetActiveChainVector(std::vector<CBlockIndex*>& chain)
{
chain.clear();
if (!pindexBest)
throw runtime_error("recalculatesupply: no best block");
for (CBlockIndex* pindex = pindexBest; pindex; pindex = pindex->pprev)
chain.push_back(pindex);
std::reverse(chain.begin(), chain.end());
}
static int64_t ComputeActiveChainSupplyFromBlocks(const std::vector<CBlockIndex*>& chain, int& nBlocksScanned, int& nTransactionsScanned)
{
nBlocksScanned = 0;
nTransactionsScanned = 0;
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)
{
CBlockIndex* pindex = *pindexIt;
if (!pindex)
throw runtime_error("recalculatesupply: null active-chain block index");
if (pindex->nHeight == 0)
{
nBlocksScanned++;
continue;
}
CBlock block;
int64_t nBlockValueIn = 0;
int64_t nBlockValueOut = 0;
if (!block.ReadFromDisk(pindex))
throw runtime_error(strprintf("recalculatesupply: failed reading block at height %d", pindex->nHeight));
for (std::vector<CTransaction>::const_iterator txIt = block.vtx.begin(); txIt != block.vtx.end(); ++txIt)
{
const CTransaction& tx = *txIt;
nTransactionsScanned++;
nBlockValueOut += tx.GetValueOut();
if (!tx.IsCoinBase())
{
for (std::vector<CTxIn>::const_iterator txinIt = tx.vin.begin(); txinIt != tx.vin.end(); ++txinIt)
{
const CTxIn& txin = *txinIt;
CTxIndex txindex;
CTransaction txPrev;
if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
throw runtime_error(strprintf(
"recalculatesupply: failed reading prevout %s:%u while processing height %d",
txin.prevout.hash.ToString().c_str(), txin.prevout.n, pindex->nHeight));
if (txin.prevout.n >= txPrev.vout.size())
throw runtime_error(strprintf(
"recalculatesupply: prevout index %u out of range for tx %s at height %d",
txin.prevout.n, txin.prevout.hash.ToString().c_str(), pindex->nHeight));
nBlockValueIn += txPrev.vout[txin.prevout.n].nValue;
}
}
}
nSupply += (nBlockValueOut - nBlockValueIn);
nBlocksScanned++;
}
return nSupply;
}
Value recalculatesupply(const Array& params, bool fHelp)
{
if (fHelp || params.size() > 1)
throw runtime_error(
"recalculatesupply [apply=false]\n"
"Rebuilds money supply by walking the active chain from genesis and summing (valueOut - valueIn) per block.\n"
"Also returns the current UTXO-set total for comparison.\n"
"If apply=true, rewrites nMoneySupply for every block on the active chain and persists the repaired values.\n"
"\nThis is intended for repairing corrupted money-supply tracking after chain/index incidents.");
bool fApply = false;
if (params.size() == 1)
fApply = params[0].get_bool();
LOCK(cs_main);
if (!pindexBest)
throw runtime_error("recalculatesupply: no best block");
auto txdbRead_holder = MakeChainDB("r"); CTxDBBase& txdbRead = *txdbRead_holder;
int nUtxoCount = 0;
int64_t nUtxoSupply = txdbRead.SumUtxoValues(nUtxoCount);
std::vector<CBlockIndex*> activeChain;
GetActiveChainVector(activeChain);
int nBlocksScanned = 0;
int nTransactionsScanned = 0;
int64_t nHistoricalSupply = ComputeActiveChainSupplyFromBlocks(activeChain, nBlocksScanned, nTransactionsScanned);
int64_t nOldTipSupply = pindexBest->nMoneySupply;
if (fApply)
{
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)
{
CBlockIndex* pindex = *pindexIt;
if (!pindex)
throw runtime_error("recalculatesupply: null active-chain block index during apply");
if (pindex->nHeight == 0)
{
pindex->nMoneySupply = 0;
if (!txdbWrite.WriteBlockIndex(CDiskBlockIndex(pindex)))
throw runtime_error("recalculatesupply: failed to persist genesis block index during apply");
continue;
}
CBlock block;
int64_t nBlockValueIn = 0;
int64_t nBlockValueOut = 0;
if (!block.ReadFromDisk(pindex))
throw runtime_error(strprintf("recalculatesupply: failed reading block at height %d during apply", pindex->nHeight));
for (std::vector<CTransaction>::const_iterator txIt = block.vtx.begin(); txIt != block.vtx.end(); ++txIt)
{
const CTransaction& tx = *txIt;
nBlockValueOut += tx.GetValueOut();
if (!tx.IsCoinBase())
{
for (std::vector<CTxIn>::const_iterator txinIt = tx.vin.begin(); txinIt != tx.vin.end(); ++txinIt)
{
const CTxIn& txin = *txinIt;
CTxIndex txindex;
CTransaction txPrev;
if (!txPrev.ReadFromDisk(txdbWrite, txin.prevout, txindex))
throw runtime_error(strprintf(
"recalculatesupply: failed reading prevout %s:%u during apply at height %d",
txin.prevout.hash.ToString().c_str(), txin.prevout.n, pindex->nHeight));
if (txin.prevout.n >= txPrev.vout.size())
throw runtime_error(strprintf(
"recalculatesupply: prevout index %u out of range during apply for tx %s at height %d",
txin.prevout.n, txin.prevout.hash.ToString().c_str(), pindex->nHeight));
nBlockValueIn += txPrev.vout[txin.prevout.n].nValue;
}
}
}
nRunningSupply += (nBlockValueOut - nBlockValueIn);
pindex->nMoneySupply = nRunningSupply;
if (!txdbWrite.WriteBlockIndex(CDiskBlockIndex(pindex)))
throw runtime_error(strprintf("recalculatesupply: failed to persist block index at height %d", pindex->nHeight));
}
}
Object result;
result.push_back(Pair("height", (int)nBestHeight));
result.push_back(Pair("tip_bestblock", hashBestChain.GetHex()));
result.push_back(Pair("old_tip_supply", ValueFromAmount(nOldTipSupply)));
result.push_back(Pair("recalculated_chain_supply", ValueFromAmount(nHistoricalSupply)));
result.push_back(Pair("utxo_supply", ValueFromAmount(nUtxoSupply)));
result.push_back(Pair("tip_vs_recalculated", ValueFromAmount(nHistoricalSupply - nOldTipSupply)));
result.push_back(Pair("utxo_vs_recalculated", ValueFromAmount(nHistoricalSupply - nUtxoSupply)));
result.push_back(Pair("blocks_scanned", nBlocksScanned));
result.push_back(Pair("transactions_scanned", nTransactionsScanned));
result.push_back(Pair("utxo_count", nUtxoCount));
result.push_back(Pair("applied", fApply));
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)
{
@@ -410,12 +708,54 @@ Value getblockchaininfo(const Array& params, bool fHelp)
obj.push_back(Pair("difficulty", diff));
obj.push_back(Pair("moneysupply", ValueFromAmount(pindexBest->nMoneySupply)));
obj.push_back(Pair("timeoffset", (boost::int64_t)GetTimeOffset()));
obj.push_back(Pair("timeoffset", (int64_t)GetTimeOffset()));
obj.push_back(Pair("connections", (int)vNodes.size()));
obj.push_back(Pair("errors", GetWarnings("statusbar")));
return obj;
}
Value gencheckpoints(const Array& params, bool fHelp)
{
if (fHelp || params.size() > 1)
throw runtime_error(
"gencheckpoints [interval]\n"
"Generates hardcoded checkpoint entries for checkpoints.cpp.\n"
"Outputs C++ map entries for every <interval> blocks (default 5000)\n"
"from genesis to current tip, ready to paste into the source code.");
int nInterval = 5000;
if (params.size() > 0)
nInterval = params[0].get_int();
if (nInterval < 1)
throw runtime_error("Interval must be >= 1");
std::string result;
result += "// Generated by gencheckpoints RPC at height " + std::to_string(nBestHeight) + "\n";
result += "static MapCheckpoints mapCheckpoints = {\n";
// Always include genesis
CBlockIndex* pindex = mapBlockIndex[hashBestChain];
while (pindex->pprev)
pindex = pindex->pprev;
bool first = true;
while (pindex)
{
if (pindex->nHeight % nInterval == 0 || pindex->nHeight == nBestHeight)
{
if (!first)
result += ",\n";
result += " {" + std::to_string(pindex->nHeight) + ", uint256(\"0x"
+ pindex->GetBlockHash().GetHex() + "\")}";
first = false;
}
pindex = pindex->pnext;
}
result += "\n};\n";
return result;
}
// ============================================================================
// Address index RPC commands
// ============================================================================
@@ -478,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;
}
@@ -513,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++)
{
@@ -573,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;
@@ -598,3 +938,212 @@ Value getaddresstxids(const Array& params, bool fHelp)
return result;
}
Value getchaintips(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 0)
throw runtime_error(
"getchaintips\n"
"Return information about all known tips in the block tree,\n"
"including the main chain as well as orphaned branches.\n"
"Essential for diagnosing chain forks.");
// Collect all block indices that are tips (nothing points to them as pprev)
set<CBlockIndex*> setTips;
{
LOCK(cs_main);
for (const auto& item : mapBlockIndex)
setTips.insert(item.second);
for (const auto& item : mapBlockIndex) {
if (item.second->pprev)
setTips.erase(item.second->pprev);
}
}
Array res;
LOCK(cs_main);
for (CBlockIndex* tip : setTips)
{
Object obj;
obj.push_back(Pair("height", tip->nHeight));
obj.push_back(Pair("hash", tip->GetBlockHash().GetHex()));
obj.push_back(Pair("chaintrust", tip->nChainTrust.GetHex()));
int branchLen = 0;
CBlockIndex* pWalk = tip;
while (pWalk && !pWalk->IsInMainChain()) {
branchLen++;
pWalk = pWalk->pprev;
}
string status;
if (tip == pindexBest)
status = "active";
else if (branchLen > 0)
status = "valid-fork";
else
status = "unknown";
obj.push_back(Pair("branchlen", branchLen));
obj.push_back(Pair("status", status));
if (pWalk && !tip->IsInMainChain())
obj.push_back(Pair("forkpoint", pWalk->GetBlockHash().GetHex()));
res.push_back(obj);
}
return res;
}
Value invalidateblock(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 1)
throw runtime_error(
"invalidateblock <hash>\n"
"Permanently marks a block as invalid and rewinds the chain.\n"
"This forces the node to reorganize to the parent chain.\n"
"Use reconsiderblock to undo.");
string strHash = params[0].get_str();
uint256 hash(strHash);
LOCK(cs_main);
if (mapBlockIndex.count(hash) == 0)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
CBlockIndex* pindex = mapBlockIndex[hash];
if (pindex->IsInMainChain())
{
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
if (!txdb.TxnBegin())
throw runtime_error("Failed to begin transaction.");
CBlockIndex* pindexWalk = pindexBest;
// Disconnect blocks from best back to (but not including) pindex's parent
while (pindexWalk && pindexWalk != pindex->pprev)
{
CBlock block;
if (!block.ReadFromDisk(pindexWalk))
throw runtime_error("Failed to read block from disk during invalidation.");
if (!block.DisconnectBlock(txdb, pindexWalk))
throw runtime_error("Failed to disconnect block during invalidation.");
// Remove disconnected PoS blocks from setStakeSeen
if (pindexWalk->IsProofOfStake())
{
extern set<pair<COutPoint, unsigned int> > setStakeSeen;
setStakeSeen.erase(make_pair(pindexWalk->prevoutStake, pindexWalk->nStakeTime));
}
pindexWalk->pprev->pnext = nullptr;
pindexWalk = pindexWalk->pprev;
}
// Update best block to the fork point
if (pindex->pprev) {
pindexBest = pindex->pprev;
extern uint256 nBestChainTrust;
nBestChainTrust = pindexBest->nChainTrust;
nBestHeight = pindexBest->nHeight;
txdb.WriteHashBestChain(pindexBest->GetBlockHash());
if (!txdb.TxnCommit())
throw runtime_error("Failed to commit transaction.");
printf("invalidateblock: rewound chain to height %d hash %s\n",
pindexBest->nHeight, pindexBest->GetBlockHash().ToString().c_str());
}
}
return Value::null;
}
Value reconsiderblock(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 1)
throw runtime_error(
"reconsiderblock <hash>\n"
"Reconsiders a previously invalidated block for activation.\n"
"If it has more chain trust than current best, triggers a reorg.");
string strHash = params[0].get_str();
uint256 hash(strHash);
LOCK(cs_main);
if (mapBlockIndex.count(hash) == 0)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
CBlockIndex* pindex = mapBlockIndex[hash];
extern uint256 nBestChainTrust;
if (pindex->nChainTrust > nBestChainTrust)
{
CBlock block;
if (!block.ReadFromDisk(pindex))
throw runtime_error("Failed to read block from disk.");
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);
}
else
{
printf("reconsiderblock: block %s does not have more trust than current best\n",
hash.ToString().c_str());
}
return Value::null;
}
Value dumputxoset(const Array& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
throw runtime_error(
"dumputxoset <filename> [nheaders]\n"
"Dumps the current UTXO set and recent block headers to a binary snapshot file.\n"
"The snapshot can be used by new nodes to skip initial block download.\n"
"\nArguments:\n"
"1. filename (string, required) Destination file path\n"
"2. nheaders (int, optional, default=2000) Number of block headers to include\n"
"\nResult:\n"
"{\n"
" \"filename\": \"...\",\n"
" \"height\": n,\n"
" \"blockhash\": \"...\",\n"
" \"file_size\": n\n"
"}");
string filename = params[0].get_str();
unsigned int nHeaders = UTXO_SNAPSHOT_DEFAULT_HEADERS;
if (params.size() > 1)
nHeaders = params[1].get_int();
if (nHeaders < 100)
throw JSONRPCError(RPC_INVALID_PARAMETER, "nheaders must be at least 100");
std::filesystem::path destPath(filename);
std::string strError;
if (!UtxoSnapshot::DumpSnapshot(destPath, nHeaders, strError))
throw runtime_error("dumputxoset failed: " + strError);
// Get file size
int64_t nFileSize = 0;
if (std::filesystem::exists(destPath))
nFileSize = (int64_t)std::filesystem::file_size(destPath);
Object result;
result.push_back(Pair("filename", filename));
result.push_back(Pair("height", nBestHeight));
result.push_back(Pair("blockhash", hashBestChain.GetHex()));
result.push_back(Pair("file_size", nFileSize));
return result;
}
+6 -8
View File
@@ -11,7 +11,6 @@
#include "base58.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/algorithm/string.hpp>
#define printf OutputDebugStringF
@@ -94,9 +93,9 @@ public:
bool fSpent;
CWalletTx* ptx;
int nOut;
CTxDump(CWalletTx* ptx = NULL, int nOut = -1)
CTxDump(CWalletTx* ptx = nullptr, int nOut = -1)
{
pindex = NULL;
pindex = nullptr;
nValue = 0;
fSpent = false;
this->ptx = ptx;
@@ -167,8 +166,7 @@ Value importwallet(const Array& params, bool fHelp)
if (line.empty() || line[0] == '#')
continue;
std::vector<std::string> vstr;
boost::split(vstr, line, boost::is_any_of(" "));
auto vstr = SplitString(line, ' ');
if (vstr.size() < 2)
continue;
CTrianglesSecret vchSecret;
@@ -189,13 +187,13 @@ Value importwallet(const Array& params, bool fHelp)
std::string strLabel;
bool fLabel = true;
for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
if (boost::algorithm::starts_with(vstr[nStr], "#"))
if (vstr[nStr].starts_with("#"))
break;
if (vstr[nStr] == "change=1")
fLabel = false;
if (vstr[nStr] == "reserve=1")
fLabel = false;
if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
if (vstr[nStr].starts_with("label=")) {
strLabel = DecodeDumpString(vstr[nStr].substr(6));
fLabel = true;
}
@@ -281,7 +279,7 @@ Value dumpwallet(const Array& params, bool fHelp)
// sort time/key pairs
std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
for (std::map<CKeyID, int64_t>::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) {
vKeyBirth.push_back(std::make_pair(it->second, it->first));
vKeyBirth.push_back({it->second, it->first});
}
mapKeyBirth.clear();
std::sort(vKeyBirth.begin(), vKeyBirth.end());
+32 -1
View File
@@ -78,7 +78,8 @@ Value getstakinginfo(const Array& params, bool fHelp)
if (fHelp || params.size() != 0)
throw runtime_error(
"getstakinginfo\n"
"Returns an object containing staking-related information.");
"Returns an object containing staking-related information.\n"
"Includes diagnostic details about why staking may be disabled.");
uint64_t nMinWeight = 0, nMaxWeight = 0, nWeight = 0;
pwalletMain->GetStakeWeight(*pwalletMain, nMinWeight, nMaxWeight, nWeight);
@@ -87,6 +88,26 @@ Value getstakinginfo(const Array& params, bool fHelp)
bool staking = nLastCoinStakeSearchInterval && nWeight;
int nExpectedTime = staking ? (nTargetSpacing * nNetworkWeight / nWeight) : -1;
// Diagnostic: determine why staking might be disabled
Array stakingDisabledReasons;
if (!GetBoolArg("-staking", true))
stakingDisabledReasons.push_back("staking disabled via -staking=0 flag");
if (pwalletMain->IsLocked())
stakingDisabledReasons.push_back("wallet is locked (use walletpassphrase <pw> <timeout> true)");
if (vNodes.empty())
stakingDisabledReasons.push_back("no network connections (need at least 1 peer)");
if (IsInitialBlockDownload())
stakingDisabledReasons.push_back("initial block download in progress");
if (nWeight == 0)
stakingDisabledReasons.push_back("no mature coins available (coins need 520 confirmations)");
if (!staking && nLastCoinStakeSearchInterval == 0)
stakingDisabledReasons.push_back("stake miner thread not running");
Object obj;
obj.push_back(Pair("enabled", GetBoolArg("-staking", true)));
@@ -105,6 +126,16 @@ Value getstakinginfo(const Array& params, bool fHelp)
obj.push_back(Pair("expectedtime", nExpectedTime));
// Add detailed diagnostics
obj.push_back(Pair("walletlocked", pwalletMain->IsLocked()));
obj.push_back(Pair("walletunlockedforstakingonly", fWalletUnlockStakingOnly));
obj.push_back(Pair("connections", (int)vNodes.size()));
obj.push_back(Pair("initialblockdownload", IsInitialBlockDownload()));
obj.push_back(Pair("maturecoins", nWeight > 0));
if (!stakingDisabledReasons.empty())
obj.push_back(Pair("staking_disabled_reasons", stakingDisabledReasons));
return obj;
}
+152 -62
View File
@@ -2,10 +2,10 @@
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <algorithm>
#include "net.h"
#include "addrman.h"
#include "trianglesrpc.h"
#include "alert.h"
#include "wallet.h"
#include "db.h"
#include "walletdb.h"
@@ -31,7 +31,7 @@ Value getnetworkinfo(const Array& params, bool fHelp)
healthObj.push_back(Pair("torpeers", health.torPeers));
healthObj.push_back(Pair("bootstrapped", health.isBootstrapped));
healthObj.push_back(Pair("syncing", health.isSyncing));
healthObj.push_back(Pair("lastblocktime", static_cast<boost::int64_t>(health.lastBlockTime)));
healthObj.push_back(Pair("lastblocktime", static_cast<int64_t>(health.lastBlockTime)));
healthObj.push_back(Pair("networkmode", "tor_native"));
Object obj;
@@ -88,14 +88,17 @@ Value getpeerinfo(const Array& params, bool fHelp)
obj.push_back(Pair("addr", stats.addrName));
obj.push_back(Pair("services", strprintf("%08"PRIx64, stats.nServices)));
obj.push_back(Pair("lastsend", (boost::int64_t)stats.nLastSend));
obj.push_back(Pair("lastrecv", (boost::int64_t)stats.nLastRecv));
obj.push_back(Pair("conntime", (boost::int64_t)stats.nTimeConnected));
obj.push_back(Pair("lastsend", (int64_t)stats.nLastSend));
obj.push_back(Pair("lastrecv", (int64_t)stats.nLastRecv));
obj.push_back(Pair("conntime", (int64_t)stats.nTimeConnected));
obj.push_back(Pair("version", stats.nVersion));
obj.push_back(Pair("subver", stats.strSubVer));
obj.push_back(Pair("inbound", stats.fInbound));
obj.push_back(Pair("startingheight", stats.nStartingHeight));
obj.push_back(Pair("banscore", stats.nMisbehavior));
obj.push_back(Pair("pingtime", stats.nPingUsecTime > 0 ? (double)stats.nPingUsecTime / 1000000.0 : -1.0));
obj.push_back(Pair("blocksdelivered", stats.nBlocksDelivered));
obj.push_back(Pair("avglatency", stats.nAvgBlockLatencyUs > 0 ? (double)stats.nAvgBlockLatencyUs / 1000.0 : -1.0));
ret.push_back(obj);
}
@@ -103,70 +106,75 @@ 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)
Value addnode(const Array& params, bool fHelp)
{
if (fHelp || params.size() < 6)
string strCommand;
if (params.size() == 2)
strCommand = params[1].get_str();
if (fHelp || params.size() != 2 ||
(strCommand != "onetry" && strCommand != "add" && strCommand != "remove"))
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.");
"addnode <node> <add|remove|onetry>\n"
"Attempts to add or remove a node from the addnode list,\n"
"or try a connection to a node once.\n"
"<node> must be a .onion address (Tor-native network).");
CAlert alert;
CKey key;
string strNode = params[0].get_str();
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;
// Tor-native: require .onion addresses
if (strNode.find(".onion") == string::npos)
throw runtime_error("Only .onion addresses are supported on this network.");
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
if (strCommand == "onetry")
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
alert.RelayTo(pnode);
CAddress addr;
CNode* pnode = ConnectNode(addr, strNode.c_str());
if (!pnode)
throw runtime_error("Failed to connect to node (may already be connected or unreachable).");
pnode->Release();
return Value::null;
}
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;
// For add/remove, manipulate the -addnode list that ThreadOpenAddedConnections uses
LOCK(cs_vNodes);
vector<string>& vAddedNodes = mapMultiArgs["-addnode"];
if (strCommand == "add")
{
for (const string& existing : vAddedNodes)
if (existing == strNode)
throw runtime_error("Node already added.");
vAddedNodes.push_back(strNode);
}
else if (strCommand == "remove")
{
auto it = std::find(vAddedNodes.begin(), vAddedNodes.end(), strNode);
if (it == vAddedNodes.end())
throw runtime_error("Node not found in addnode list.");
vAddedNodes.erase(it);
}
return Value::null;
}
Value disconnectnode(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 1)
throw runtime_error(
"disconnectnode <node>\n"
"Immediately disconnects from the specified node.");
string strNode = params[0].get_str();
LOCK(cs_vNodes);
for (CNode* pnode : vNodes) {
if (pnode->addrName == strNode || pnode->addr.ToString() == strNode) {
pnode->CloseSocketDisconnect();
return Value::null;
}
}
throw runtime_error("Node not found.");
}
Value getseedlist(const Array& params, bool fHelp)
@@ -187,9 +195,91 @@ Value getseedlist(const Array& params, bool fHelp)
Object obj;
obj.push_back(Pair("address", addr.ToStringIP()));
obj.push_back(Pair("port", (int)addr.GetPort()));
obj.push_back(Pair("lastseen", (boost::int64_t)addr.nTime));
obj.push_back(Pair("lastseen", (int64_t)addr.nTime));
ret.push_back(obj);
}
return ret;
}
Value getnetworkstability(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 0)
throw runtime_error(
"getnetworkstability\n"
"Returns detailed network stability metrics including peer quality,\n"
"connection health, and isolation risk assessment.");
int nOutbound = 0, nInbound = 0, nTotal = 0;
int64_t nBestPing = INT64_MAX, nWorstPing = 0, nTotalPing = 0;
int nPingCount = 0;
int nTotalBlocksDelivered = 0;
int64_t nOldestConnection = 0;
int64_t nNewestConnection = INT64_MAX;
{
LOCK(cs_vNodes);
nTotal = vNodes.size();
for (CNode* pnode : vNodes) {
if (pnode->fInbound)
nInbound++;
else
nOutbound++;
if (pnode->nPingUsecTime > 0) {
nTotalPing += pnode->nPingUsecTime;
nPingCount++;
if (pnode->nPingUsecTime < nBestPing)
nBestPing = pnode->nPingUsecTime;
if (pnode->nPingUsecTime > nWorstPing)
nWorstPing = pnode->nPingUsecTime;
}
nTotalBlocksDelivered += pnode->nBlocksDelivered;
int64_t uptime = GetTime() - pnode->nTimeConnected;
if (uptime > nOldestConnection)
nOldestConnection = uptime;
if (uptime < nNewestConnection)
nNewestConnection = uptime;
}
}
// Determine isolation risk
string strRisk;
if (nOutbound == 0 && nInbound == 0)
strRisk = "critical";
else if (nOutbound == 0)
strRisk = "high";
else if (nOutbound == 1)
strRisk = "elevated";
else if (nOutbound < 3)
strRisk = "moderate";
else
strRisk = "low";
Object obj;
obj.push_back(Pair("connections_total", nTotal));
obj.push_back(Pair("connections_outbound", nOutbound));
obj.push_back(Pair("connections_inbound", nInbound));
obj.push_back(Pair("isolation_risk", strRisk));
obj.push_back(Pair("blocks_delivered_total", nTotalBlocksDelivered));
obj.push_back(Pair("known_addresses", (int)addrman.size()));
Object pingObj;
pingObj.push_back(Pair("best_ms", nPingCount > 0 ? (double)nBestPing / 1000.0 : -1.0));
pingObj.push_back(Pair("worst_ms", nPingCount > 0 ? (double)nWorstPing / 1000.0 : -1.0));
pingObj.push_back(Pair("avg_ms", nPingCount > 0 ? (double)nTotalPing / nPingCount / 1000.0 : -1.0));
pingObj.push_back(Pair("peers_measured", nPingCount));
obj.push_back(Pair("ping", pingObj));
Object uptimeObj;
uptimeObj.push_back(Pair("newest_sec", nTotal > 0 ? (int64_t)nNewestConnection : 0));
uptimeObj.push_back(Pair("oldest_sec", nTotal > 0 ? (int64_t)nOldestConnection : 0));
obj.push_back(Pair("connection_uptime", uptimeObj));
obj.push_back(Pair("seconds_since_last_block", (int64_t)(GetTime() - nTimeBestReceived)));
obj.push_back(Pair("current_height", nBestHeight));
return obj;
}
+12 -12
View File
@@ -17,7 +17,7 @@ using namespace json_spirit;
void ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out, bool fIncludeHex)
{
txnouttype type;
TxnOutType type;
vector<CTxDestination> addresses;
int nRequired;
@@ -28,7 +28,7 @@ void ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out, bool fIncludeH
if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired))
{
out.push_back(Pair("type", GetTxnOutputType(TX_NONSTANDARD)));
out.push_back(Pair("type", GetTxnOutputType(TxnOutType::NonStandard)));
return;
}
@@ -45,8 +45,8 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry)
{
entry.push_back(Pair("txid", tx.GetHash().GetHex()));
entry.push_back(Pair("version", tx.nVersion));
entry.push_back(Pair("time", (boost::int64_t)tx.nTime));
entry.push_back(Pair("locktime", (boost::int64_t)tx.nLockTime));
entry.push_back(Pair("time", (int64_t)tx.nTime));
entry.push_back(Pair("locktime", (int64_t)tx.nLockTime));
Array vin;
for (const CTxIn& txin : tx.vin)
{
@@ -56,13 +56,13 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry)
else
{
in.push_back(Pair("txid", txin.prevout.hash.GetHex()));
in.push_back(Pair("vout", (boost::int64_t)txin.prevout.n));
in.push_back(Pair("vout", (int64_t)txin.prevout.n));
Object o;
o.push_back(Pair("asm", txin.scriptSig.ToString()));
o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())));
in.push_back(Pair("scriptSig", o));
}
in.push_back(Pair("sequence", (boost::int64_t)txin.nSequence));
in.push_back(Pair("sequence", (int64_t)txin.nSequence));
vin.push_back(in);
}
entry.push_back(Pair("vin", vin));
@@ -72,7 +72,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry)
const CTxOut& txout = tx.vout[i];
Object out;
out.push_back(Pair("value", ValueFromAmount(txout.nValue)));
out.push_back(Pair("n", (boost::int64_t)i));
out.push_back(Pair("n", (int64_t)i));
Object o;
ScriptPubKeyToJSON(txout.scriptPubKey, o, false);
out.push_back(Pair("scriptPubKey", o));
@@ -90,8 +90,8 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry)
if (pindex->IsInMainChain())
{
entry.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight));
entry.push_back(Pair("time", (boost::int64_t)pindex->nTime));
entry.push_back(Pair("blocktime", (boost::int64_t)pindex->nTime));
entry.push_back(Pair("time", (int64_t)pindex->nTime));
entry.push_back(Pair("blocktime", (int64_t)pindex->nTime));
}
else
entry.push_back(Pair("confirmations", 0));
@@ -371,7 +371,7 @@ Value signrawtransaction(const Array& params, bool fHelp)
CTransaction tempTx;
MapPrevTx mapPrevTx;
MapPrevTx mapEmpty;
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
bool fInvalid;
// FetchInputs aborts on failure, so we go one at a time.
@@ -548,11 +548,11 @@ Value sendrawtransaction(const Array& params, bool fHelp)
else
{
// push to local node
CTxDB txdb("r");
auto txdb_holder = MakeChainDB("r"); CTxDBBase& txdb = *txdb_holder;
if (!tx.AcceptToMemoryPool(txdb))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX rejected");
SyncWithWallets(tx, NULL, true);
SyncWithWallets(tx, nullptr, true);
}
RelayTransaction(tx, hashTx);

Some files were not shown because too many files have changed in this diff Show More