Compare commits

...

14 Commits

Author SHA1 Message Date
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
sami7777 029f5a4bfc Fix consensus bugs causing persistent chain splits (v5.7.8)
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
Three fixes for the fork-oscillation problem where same-version nodes
keep disagreeing on the chain tip:

1. Prune setStakeSeen on reorg — disconnected PoS blocks' stake entries
   were never removed, blocking acceptance of valid competing blocks
   and preventing chain convergence after reorganizations.

2. Remove global nBestHeight from PastDrift/FutureDrift — the no-argument
   overloads used the mutable global nBestHeight to decide between 3-min
   and 10-min timestamp drift at the V5.4 fork boundary (block 2186941).
   Nodes at different heights applied different validation rules to the
   same block, causing a permanent consensus split. Now always uses
   post-fork 3-min rules since all nodes are well past the fork.

3. Anti-oscillation for equal-trust reorgs — the hash-based tiebreaker
   now only fires for shallow forks (parent in main chain). Deep forks
   with equal trust no longer trigger reorgs, preventing the Tor-latency-
   induced ping-pong where nodes flip between competing chains.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 17:17:07 -07:00
32 changed files with 1736 additions and 240 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ if(POLICY CMP0167)
endif()
project(Triangles
VERSION 5.7.6.0
VERSION 5.8.3
DESCRIPTION "Cryptographic Triangles Wallet"
LANGUAGES C CXX
)
+210
View File
@@ -0,0 +1,210 @@
# Triangles Modernization Roadmap
**Goal:** Make TRI faster to sync, safer for wallets, and more useful as a currency — without breaking consensus.
**Invariant:** Any change that modifies block validation, stake modifier computation, transaction format, or signature verification MUST preserve exact consensus with existing v5.x nodes. When in doubt, test against a synced v5.8.1 node.
---
## Priority 1: Faster Syncing (High Impact, Low Risk)
### 1.1 Update Checkpoints (Easy, Immediate)
**Problem:** Last hardcoded checkpoint is at block 2,186,940. `IsInitialBlockDownload()` returns false past this point, causing orphan limit to drop from 4000 to 750 — exactly what caused the fork deadlock.
**Fix:** Add checkpoints every ~50,000 blocks up to current height (~2,207,000+).
```cpp
// src/checkpoints.cpp - add recent checkpoints
{2190000, uint256("...")},
{2195000, uint256("...")},
{2200000, uint256("...")},
{2205000, uint256("...")},
{2210000, uint256("...")},
```
**Risk:** None — checkpoints are only used for IBD detection and quick rejection of clearly wrong chains.
### 1.2 Increase Post-Checkpoint Orphan Limit (Easy)
**Problem:** 750 orphans after IBD is too low for a low-peer network. During the fork incident, 750 orphans filled up and the node deadlocked.
**Fix:**
```cpp
// src/main.h
static const unsigned int MAX_ORPHAN_BLOCKS = 2000; // was 750
```
**Risk:** Slightly more memory usage during forks. Worth it for resilience.
### 1.3 Parallel Block Download (Medium Effort)
**Problem:** Current implementation downloads blocks sequentially from one peer at a time during IBD.
**Fix:** Increase batch sizes and allow concurrent block downloads from multiple peers:
```cpp
// src/main.cpp
// During IBD, request blocks from multiple peers simultaneously
unsigned int nGetDataBatchSize = IsInitialBlockDownload() ? 8000 : 1000; // was 4000
```
**Risk:** Low — larger batch sizes are already proven in Bitcoin forks.
### 1.4 Header-First Sync (Medium Effort)
**Problem:** Node downloads full blocks before validating headers. A bad peer can waste bandwidth.
**Fix:** Download and validate all headers first (compact ~80 bytes each), then download full blocks only for the best chain.
- Separate `getheaders`/`headers` message handling
- Download blocks only for the best header chain
- Reduces wasted bandwidth during forks by 95%+
### 1.5 Bootstrap Over HTTPS with Resume (Easy)
**Problem:** Built-in bootstrap (`-bootstrap`) uses raw TCP and can't resume interrupted downloads.
**Fix:** The existing `bootstrap.cpp` already supports downloading. Add:
- Resume support (Range headers)
- SHA256 verification of downloaded archive
- Better progress reporting
- Fallback mirrors
---
## Priority 2: Wallet Safety (Critical)
### 2.1 Automatic Wallet Backup Before Dangerous Operations (Easy)
**Problem:** Corrupt wallet = lost funds. No automatic backup before risky operations.
**Fix:** In `walletdb.cpp`, before any rewrite:
```cpp
// Before wallet.dat rewrite, copy to wallet.dat.bak
if (boost::filesystem::exists(pathWallet)) {
boost::filesystem::copy_file(pathWallet, pathWallet + ".bak",
boost::filesystem::copy_option::overwrite_if_exists);
}
```
### 2.2 Detect and Report BDB Corruption (Easy)
**Problem:** BDB corruption silently corrupts wallet. User doesn't know until it's too late.
**Fix:** Add wallet integrity check on load:
```cpp
// In CWallet::LoadWallet()
// After opening, verify BDB environment is healthy
// If DB_RUNRECOVERY, auto-salvage and warn user
```
### 2.3 Wallet.dat Versioning (Medium Effort)
**Problem:** Single wallet.dat file. If it corrupts during write, funds are lost.
**Fix:** Implement copy-on-write wallet saves:
- Write new wallet data to `wallet.dat.new`
- Atomically rename `wallet.dat``wallet.dat.old`, `wallet.dat.new``wallet.dat`
- Keep last 3 wallet revisions
- On load, try wallet.dat first, fall back to wallet.dat.old if corrupt
### 2.4 Seed Phrase / HD Wallet (High Effort, High Impact)
**Problem:** Losing wallet.dat = losing everything. No recovery mechanism.
**Fix:** Implement BIP39/BIP44 HD wallet as optional upgrade:
- Generate 12/24-word seed phrase on new wallet creation
- Derive all keys from seed deterministically
- Import seed on any device to recover wallet
- Keep backward compatibility with existing non-HD wallets
---
## Priority 3: Network Resilience (Medium Impact)
### 3.1 Better Peer Management (Medium Effort)
**Problem:** Low peer counts (2-6) lead to fork divergence. No prioritization of reliable peers.
**Fix:**
- Peer reliability scoring (track which peers provide valid blocks)
- Prefer peers that are ahead and on the same chain
- Automatic disconnection of stale/forked peers
- Increase default `maxconnections` from 64 to 128
### 3.2 Compact Block Relay (High Effort)
**Problem:** Full blocks are sent even when the receiver likely already has most transactions.
**Fix:** Implement BIP 152 compact blocks:
- Send block header + short transaction IDs
- Receiver fills in from mempool, only requests missing transactions
- Reduces bandwidth by ~90% during normal operation
### 3.3 DNS Seed Infrastructure (Easy)
**Problem:** `dnsseed=0` when Tor-only means no automatic peer discovery.
**Fix:** Run a DNS seed server that resolves to known reliable onion addresses:
```
seed.cryptographic-triangles.org → returns onion addresses of healthy nodes
```
---
## Priority 4: User Experience (Medium Impact)
### 4.1 Progress Reporting for IBD (Easy)
**Problem:** Users see "downloading blocks..." with no useful progress indicator.
**Fix:**
- Report `headers` vs `blocks` progress separately
- Show estimated time remaining based on download speed
- Log progress every 1000 blocks (currently every 5000)
- Qt wallet: update progress bar more frequently
### 4.2 Staking Dashboard Improvements (Easy)
**Problem:** Qt wallet shows staking info but not clearly.
**Fix:**
- Show expected time to stake more prominently
- Display staking weight as percentage of network
- Notify when stake is found (system notification)
- Show "staking" indicator in system tray
### 4.3 Transaction Fee Estimation (Medium Effort)
**Problem:** No fee estimation. Users guess.
**Fix:** Track recent block inclusion rates by fee level, provide fee recommendations.
---
## Priority 5: Code Modernization (Low Urgency, Good Hygiene)
### 5.1 C++17/20 Features
- Replace raw pointers with smart pointers where safe
- Use `std::optional`, `std::string_view`, `std::filesystem`
- Replace boost::filesystem with std::filesystem (C++17)
### 5.2 Build System
- CMake is already in place (good)
- Add sanitizers (ASAN, UBSAN) to CI
- Static analysis with clang-tidy
### 5.3 Testing
- Current test coverage is thin
- Add unit tests for:
- Checkpoint validation
- Stake modifier computation
- Bootstrap download/resume
- Wallet BDB recovery
- Orphan block handling
---
## What NOT to Change
These are consensus-critical and must remain identical:
- Block validation rules
- Stake modifier computation (`ComputeNextStakeModifier`)
- Transaction signature verification
- Block reward schedule
- PoW/PoS target computation
- Chain trust / difficulty adjustment
- Message serialization format
- Protocol version handshaking
Any change to these requires a coordinated network upgrade (hard fork).
---
## Implementation Order
1. **This week:** Update checkpoints (1.1), increase orphan limit (1.2), wallet backup before save (2.1)
2. **Next week:** Better progress reporting (4.1), increase batch size (1.3)
3. **Month 1:** Wallet versioning (2.3), bootstrap resume (1.5)
4. **Month 2:** Header-first sync (1.4), peer reliability (3.1)
5. **Month 3+:** HD wallet (2.4), compact blocks (3.2)
+300
View File
@@ -0,0 +1,300 @@
# OpenClaw Bootstrap Snapshot Guide
## Purpose
This document tells OpenClaw exactly how to update the existing Triangles bootstrap server so new wallets download a ready-to-use snapshot instead of downloading `blk0001.dat` and rebuilding the index locally.
This guide matches the current wallet code in:
- `src/bootstrap.cpp`
- `src/bootstrap.h`
- `src/checkpoints.cpp`
- `src/version.h`
## What The Wallet Actually Does
When a fresh wallet bootstraps, it:
1. Downloads `http://bootstrap.cryptographic-triangles.org/bootstrap.tar.gz`
2. Extracts it into the data directory
3. Requires `blk0001.dat` to exist after extraction
4. Looks for `txleveldb/` and `snapshot.manifest`
5. Keeps `txleveldb/` only if `snapshot.manifest` passes verification
6. Deletes `txleveldb/` if verification fails, then rebuilds from `blk0001.dat`
7. Always deletes `database/` from the extracted snapshot
The verification rules are strict:
- `format` must be `1`
- `network` must be `main` on mainnet
- `dbversion` must be `70509`
- `height` and `hash` must exactly match a hardcoded checkpoint
If any of those checks fail, the wallet throws away the shipped `txleveldb/`.
## Current Hardcoded Mainnet Checkpoint
As of the current codebase, the latest hardcoded mainnet checkpoint is:
- Height: `2186940`
- Hash: `bd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0`
OpenClaw must not generate a manifest with an arbitrary tip hash. The manifest only survives if it matches a hardcoded checkpoint from `src/checkpoints.cpp`.
## Important Limitation
If the live chain tip is past the latest hardcoded checkpoint, OpenClaw has two valid options:
1. Publish a snapshot taken exactly at the latest hardcoded checkpoint
2. Publish `blk0001.dat` only, without `txleveldb/`, and let clients rebuild locally
OpenClaw must not publish a `snapshot.manifest` for a height/hash that is not compiled into the wallet.
## Files OpenClaw Should Publish
The preferred `bootstrap.tar.gz` should contain:
- `blk0001.dat`
- `txleveldb/`
- `snapshot.manifest`
- optionally `peers.dat`
It must not contain:
- `wallet.dat`
- `database/`
- `.lock`
- pid files
- logs
- Tor state
Legacy fallback files should still exist on the web root:
- `blk0001.dat`
- `filelist.txt`
## Requirements For The Source Node
Before building a snapshot, the source node should be:
- fully synced
- cleanly shut down before copying files
- built from the same code/version expected by clients
- using the same LevelDB schema as the client (`DATABASE_VERSION=70509`)
Recommended node config for the source snapshot node:
```ini
txindex=1
addressindex=1
daemon=1
server=1
```
`addressindex=1` is recommended so clients that enable address index can benefit from faster indexed wallet rescans and address RPCs immediately.
## OpenClaw Workflow
### Step 1: Decide Whether A Prebuilt Index Is Allowed
OpenClaw must first decide whether it can ship `txleveldb/`.
Rules:
- If the snapshot node is exactly at checkpoint `2186940`, shipping `txleveldb/` is allowed
- If the snapshot node is above `2186940` and the code has not been updated with a newer checkpoint, do not ship `txleveldb/`
- In that case, publish a blocks-only bootstrap instead
### Step 2: Stop The Source Node Cleanly
Never copy a live LevelDB directory.
```bash
trianglesd stop
sleep 10
pgrep -af trianglesd || true
```
OpenClaw should confirm the daemon is fully stopped before copying `txleveldb/`.
### Step 3: Create A Staging Directory
```bash
rm -rf /tmp/triangles-bootstrap-stage
mkdir -p /tmp/triangles-bootstrap-stage
```
### Step 4: Copy Snapshot Files
For a verified snapshot:
```bash
cp ~/.triangles/blk0001.dat /tmp/triangles-bootstrap-stage/
cp -a ~/.triangles/txleveldb /tmp/triangles-bootstrap-stage/
test -f ~/.triangles/peers.dat && cp ~/.triangles/peers.dat /tmp/triangles-bootstrap-stage/
```
Do not copy:
```bash
rm -rf /tmp/triangles-bootstrap-stage/database
rm -f /tmp/triangles-bootstrap-stage/wallet.dat
rm -f /tmp/triangles-bootstrap-stage/.lock
rm -f /tmp/triangles-bootstrap-stage/*.pid
rm -f /tmp/triangles-bootstrap-stage/debug.log
```
### Step 5: Write `snapshot.manifest`
If OpenClaw is publishing a verified prebuilt index, write:
```bash
cat > /tmp/triangles-bootstrap-stage/snapshot.manifest << 'EOF'
format=1
network=main
height=2186940
hash=bd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0
dbversion=70509
EOF
```
Rules:
- `hash` must not include `0x`
- `network` must be `main`
- `dbversion` must be `70509`
- If OpenClaw is publishing blocks-only bootstrap, it should omit `snapshot.manifest` entirely
### Step 6: Build The Tarball
```bash
cd /tmp/triangles-bootstrap-stage
tar czf /tmp/bootstrap.tar.gz .
```
### Step 7: Publish To The Existing Bootstrap Server
This guide assumes the existing nginx root is:
- `/var/www/triangles-bootstrap`
Publish the preferred tarball and the legacy fallback files:
```bash
sudo mkdir -p /var/www/triangles-bootstrap
sudo mv /tmp/bootstrap.tar.gz /var/www/triangles-bootstrap/bootstrap.tar.gz
sudo cp ~/.triangles/blk0001.dat /var/www/triangles-bootstrap/blk0001.dat
printf "blk0001.dat\n" | sudo tee /var/www/triangles-bootstrap/filelist.txt > /dev/null
sudo chown -R www-data:www-data /var/www/triangles-bootstrap
```
If OpenClaw is publishing a blocks-only bootstrap, the commands are the same except the tarball should contain only `blk0001.dat` and optional `peers.dat`.
## Validation Checklist
Before marking the update complete, OpenClaw should verify:
### Tarball contents
```bash
tar tzf /var/www/triangles-bootstrap/bootstrap.tar.gz | sort
```
Expected for verified snapshot:
- `./blk0001.dat`
- `./txleveldb/...`
- `./snapshot.manifest`
Expected not to exist:
- `wallet.dat`
- `database/`
### HTTP responses
```bash
curl -I http://localhost/bootstrap.tar.gz
curl -I http://localhost/blk0001.dat
curl http://localhost/filelist.txt
```
Expected:
- HTTP `200`
- `filelist.txt` contains `blk0001.dat`
### Manifest sanity
```bash
tar xOf /var/www/triangles-bootstrap/bootstrap.tar.gz ./snapshot.manifest
```
Expected:
- `format=1`
- `network=main`
- `height=2186940`
- `hash=bd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0`
- `dbversion=70509`
## Fresh-Client Test
OpenClaw should test the artifact on a clean machine or clean data directory:
```bash
mv ~/.triangles ~/.triangles.backup.$(date +%s)
mkdir -p ~/.triangles
trianglesd -bootstrap
```
Then inspect startup logs.
Successful verified snapshot behavior should include:
- snapshot downloaded
- `snapshot.manifest found`
- `manifest verified - keeping pre-built index`
- no message about removing extracted `txleveldb/`
Failure behavior will include:
- manifest parse or verification failure
- `removing extracted txleveldb/`
- slow rebuild from `blk0001.dat`
## Safe Publish Procedure
OpenClaw should use this order:
1. Build snapshot in `/tmp`
2. Validate tarball contents
3. Replace `/var/www/triangles-bootstrap/bootstrap.tar.gz`
4. Replace `/var/www/triangles-bootstrap/blk0001.dat`
5. Replace `/var/www/triangles-bootstrap/filelist.txt`
6. Confirm HTTP `200`
This avoids serving a half-written tarball.
## Example Bot Prompt
Use this exact tasking for OpenClaw:
```text
Update the existing Triangles bootstrap server on bootstrap.cryptographic-triangles.org.
Rules:
- Build the snapshot from a cleanly stopped source node
- If the source node is exactly at hardcoded checkpoint 2186940 / bd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0, publish a verified snapshot containing blk0001.dat, txleveldb/, and snapshot.manifest
- If the source node is above the latest hardcoded checkpoint, publish a blocks-only bootstrap and do not ship txleveldb/
- Do not ship wallet.dat, database/, .lock, pid files, logs, or Tor state
- Publish bootstrap.tar.gz, blk0001.dat, and filelist.txt to /var/www/triangles-bootstrap
- Verify curl HTTP 200 for bootstrap.tar.gz and blk0001.dat
- Report the tarball contents and whether the snapshot is verified or blocks-only
```
## Recommended Next Improvement
This workflow will stay constrained until the next checkpoint is updated in `src/checkpoints.cpp`.
If you want OpenClaw to keep shipping prebuilt `txleveldb/` snapshots as the chain advances, the software needs periodic checkpoint updates. Without that, the verified snapshot path will stop at the latest compiled checkpoint and clients will fall back to rebuilds.
+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
+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}")
+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
+234 -86
View File
@@ -3,7 +3,6 @@
#include "bootstrap.h"
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/algorithm/string.hpp>
@@ -12,6 +11,8 @@
#include "version.h"
#include "uint256.h"
#include "netbase.h"
#include "net.h"
#include <fstream>
#include <sstream>
@@ -19,12 +20,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 Bootstrap {
@@ -33,112 +42,248 @@ bool NeedsBootstrap(const fs::path& dataDir)
return !fs::exists(dataDir / "blk0001.dat");
}
// Send all bytes on a raw socket
static bool SendAll(SOCKET sock, const char* data, size_t len)
{
while (len > 0) {
int n = send(sock, data, (int)std::min(len, (size_t)65536), MSG_NOSIGNAL);
if (n <= 0) return false;
data += n;
len -= n;
}
return true;
}
// Read until `delim` found in received data. Returns data including delimiter.
static bool RecvUntil(SOCKET sock, std::string& out, const std::string& delim)
{
out.clear();
char c;
while (true) {
int n = recv(sock, &c, 1, 0);
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
}
}
// 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 != NULL; 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;
}
bool DownloadFile(const std::string& host, const std::string& urlPath,
const fs::path& destPath,
ProgressCallback progressFn,
std::string& strError)
std::string& strError,
bool noProxy)
{
try {
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
std::string currentHost = host;
std::string currentPath = urlPath;
SOCKET hSocket = INVALID_SOCKET;
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;
}
tcp::socket socket(io_context);
boost::asio::connect(socket, endpoints);
// Send HTTP GET request
std::string request =
"GET " + urlPath + " HTTP/1.1\r\n"
"Host: " + host + "\r\n"
"Connection: close\r\n"
"User-Agent: Triangles\r\n"
"\r\n";
boost::asio::write(socket, boost::asio::buffer(request));
// Read response headers
boost::asio::streambuf response_buf;
boost::asio::read_until(socket, response_buf, "\r\n\r\n");
std::istream response_stream(&response_buf);
// Parse status line
std::string http_version;
unsigned int status_code = 0;
response_stream >> http_version >> status_code;
std::string status_message;
std::getline(response_stream, status_message);
if (status_code != 200) {
strError = "HTTP error " + std::to_string(status_code) + " for " + urlPath;
return false;
}
// Parse headers for Content-Length
int64_t content_length = 0;
std::string header_line;
while (std::getline(response_stream, header_line) && header_line != "\r") {
std::string lower_header = header_line;
std::transform(lower_header.begin(), lower_header.end(),
lower_header.begin(), ::tolower);
if (lower_header.find("content-length:") == 0) {
content_length = std::stoll(header_line.substr(header_line.find(':') + 1));
// Connection + redirect loop
while (true) {
if (noProxy) {
hSocket = ConnectDirectTCP(currentHost, PORT, strError);
if (hSocket == INVALID_SOCKET)
return false;
} else {
CService addr;
if (!ConnectSocketByName(addr, hSocket, currentHost.c_str(), PORT, 30)) {
strError = "Cannot connect to " + currentHost + " (check Tor proxy)";
return false;
}
}
// 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 (!SendAll(hSocket, request.data(), request.size())) {
closesocket(hSocket);
strError = "Failed to send request to " + currentHost;
return false;
}
// Read response headers
if (!RecvUntil(hSocket, headerData, "\r\n\r\n")) {
closesocket(hSocket);
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) {
closesocket(hSocket);
hSocket = INVALID_SOCKET;
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);
boost::trim(location);
// Reject HTTPS redirects (no TLS support)
if (location.compare(0, 8, "https://") == 0) {
strError = "Server redirected to HTTPS (not supported). "
"Configure bootstrap server for plain HTTP.";
return false;
}
// Parse redirect URL
if (location.compare(0, 7, "http://") == 0) {
std::string rest = location.substr(7);
size_t pathStart = rest.find('/');
if (pathStart != std::string::npos) {
currentHost = rest.substr(0, pathStart);
currentPath = rest.substr(pathStart);
} else {
currentHost = rest;
currentPath = "/";
}
// Strip port from host if present
size_t colonPos = currentHost.find(':');
if (colonPos != std::string::npos)
currentHost = currentHost.substr(0, colonPos);
} else if (!location.empty() && location[0] == '/') {
currentPath = location;
} else {
strError = "Unsupported redirect location: " + location;
return false;
}
printf("Bootstrap: redirect %d -> %s%s\n",
status_code, currentHost.c_str(), currentPath.c_str());
continue;
}
if (status_code != 200) {
closesocket(hSocket);
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
FILE* file = fopen(destPath.string().c_str(), "wb");
if (!file) {
closesocket(hSocket);
strError = "Cannot create file: " + destPath.string();
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 = recv(hSocket, chunk, sizeof(chunk), 0);
if (n < 0) {
fclose(file);
closesocket(hSocket);
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);
closesocket(hSocket);
// Verify download size if Content-Length was provided
if (content_length > 0 && bytes_written != content_length) {
@@ -158,13 +303,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
@@ -433,10 +579,12 @@ 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);
bool tarDownloaded = DownloadFile(host, tarUrl, tmpTarGz, progressFn, strError, noProxy);
if (tarDownloaded) {
bool extractOk = ExtractTarGz(tmpTarGz, dataDir, strError);
@@ -451,7 +599,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 +612,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;
}
+7 -4
View File
@@ -13,7 +13,6 @@ 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;
@@ -23,16 +22,20 @@ namespace Bootstrap {
// Check if data dir already has blockchain data
bool NeedsBootstrap(const boost::filesystem::path& dataDir);
// Download a single file via HTTP GET, write to destPath
// 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).
bool DownloadFile(const std::string& host, const std::string& urlPath,
const boost::filesystem::path& destPath,
ProgressCallback progressFn,
std::string& strError);
std::string& strError,
bool noProxy = false);
// 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.
+6
View File
@@ -33,6 +33,9 @@ namespace Checkpoints
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
};
static MapCheckpoints mapCheckpointsTestnet = {
@@ -49,6 +52,9 @@ namespace Checkpoints
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
};
bool CheckHardened(int nHeight, const uint256& hash)
+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 7
#define CLIENT_VERSION_MINOR 8
#define CLIENT_VERSION_REVISION 3
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
+1
View File
@@ -28,6 +28,7 @@ extern unsigned int nWalletDBUpdated;
void ThreadFlushWalletDB(void* parg);
bool BackupWallet(const CWallet& wallet, const std::string& strDest);
bool AutoBackupWallet(const boost::filesystem::path& walletPath);
class CDBEnv
+27 -5
View File
@@ -921,11 +921,6 @@ bool AppInit2()
};
bool success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError);
if (!success) {
host = Bootstrap::FALLBACK_HOST;
printf("\nBootstrap: primary host failed, trying fallback %s...\n", host.c_str());
success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError);
}
if (!success) {
printf("\nBootstrap: failed: %s\n", strError.c_str());
@@ -957,6 +952,18 @@ bool AppInit2()
return false;
}
// Handle -reindex: delete the LevelDB block index so it gets rebuilt
// from the raw blk*.dat files via FastImportBlockFile().
// This recalculates money supply, tx index, and UTXO set from scratch.
if (GetBoolArg("-reindex", false))
{
printf("Reindex requested: removing block index database...\n");
uiInterface.InitMessage(_("Removing block index for reindex..."));
fs::path txleveldbPath = GetDataDir() / "txleveldb";
if (fs::exists(txleveldbPath))
fs::remove_all(txleveldbPath);
}
uiInterface.InitMessage(_("Loading block index..."));
printf("Loading block index...\n");
nStart = GetTimeMillis();
@@ -1046,6 +1053,21 @@ bool AppInit2()
nStart = GetTimeMillis();
bool fFirstRun = true;
pwalletMain = new 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)
{
+6 -4
View File
@@ -334,9 +334,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 +351,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);
}
}
+287 -58
View File
@@ -116,8 +116,10 @@ static CCriticalSection cs_PostIbdWork;
static bool fPostIbdWorkStarted = false;
static const unsigned int MAX_HEADER_SYNC_CACHE = 50000;
static const unsigned int HEADER_DOWNLOAD_WINDOW = 128;
static const unsigned int HEADER_DOWNLOAD_WINDOW = 512; // Increased from 128 for parallel downloads
static const unsigned int HEADER_DOWNLOAD_PER_PEER = 64; // Max blocks to request from each peer
static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 30 * 1000000;
static const int64_t HEADER_REDUNDANT_REQUEST_MICROS = 10 * 1000000; // Request from another peer after 10s
static void ThreadPostIbdWork(void* parg)
{
@@ -247,7 +249,7 @@ static bool AddHeaderSyncNode(const CBlock& header, const uint256& hashHeader)
return false;
}
if (header.GetBlockTime() > FutureDrift(GetAdjustedTime()))
if (header.GetBlockTime() > GetTime() + 15 * 60)
{
printf("IBD-DIAG: header rejected (future time) hash=%s time=%u\n",
hashHeader.ToString().substr(0,20).c_str(), header.nTime);
@@ -405,6 +407,89 @@ static void ContinueHeaderSync(CNode* pfrom, const uint256& hashTip)
pfrom->PushMessage("getheaders", locator, uint256(0));
}
// Parallel block downloading: distribute blocks across all available peers
static unsigned int QueueHeaderSyncBlocksParallel(unsigned int nWindow)
{
if (hashBestHeaderSync == 0)
return 0;
const std::vector<uint256> vPath = GetHeaderSyncDownloadPath(hashBestHeaderSync);
if (vPath.empty())
return 0;
// Collect eligible peers
std::vector<CNode*> vEligiblePeers;
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
{
if (!pnode->fClient && pnode->nVersion != 0 && !pnode->fDisconnect)
vEligiblePeers.push_back(pnode);
}
}
if (vEligiblePeers.empty())
return 0;
const int64_t nNow = GetTime() * 1000000;
unsigned int nInFlight = CountHeaderSyncInFlight();
unsigned int nQueued = 0;
unsigned int nPeerIndex = 0;
// Distribute blocks across peers in round-robin fashion
for (std::vector<uint256>::const_iterator it = vPath.begin(); it != vPath.end(); ++it)
{
if (nInFlight + nQueued >= nWindow)
break;
std::map<uint256, CHeaderSyncNode>::iterator mi = mapHeaderSync.find(*it);
if (mi == mapHeaderSync.end())
continue;
// Check if already requested recently
bool fNeedsRequest = false;
if (!mi->second.fRequested)
{
// Never requested - request now
fNeedsRequest = true;
}
else if (nNow - mi->second.nLastRequestTime >= HEADER_REQUEST_TIMEOUT_MICROS)
{
// Timeout expired - retry
fNeedsRequest = true;
}
else if (nNow - mi->second.nLastRequestTime >= HEADER_REDUNDANT_REQUEST_MICROS)
{
// Redundant request: ask another peer if original is slow
// This creates parallel downloads for slow blocks
fNeedsRequest = true;
}
if (!fNeedsRequest)
continue;
// Round-robin across peers to distribute load
CNode* pnode = vEligiblePeers[nPeerIndex % vEligiblePeers.size()];
pnode->AskFor(CInv(MSG_BLOCK, *it));
// Update tracking (only on first request, not redundant)
if (!mi->second.fRequested || nNow - mi->second.nLastRequestTime >= HEADER_REQUEST_TIMEOUT_MICROS)
{
mi->second.fRequested = true;
mi->second.nLastRequestTime = nNow;
}
++nQueued;
++nPeerIndex;
}
if (nQueued > 0)
printf("IBD-DIAG: parallel queue distributed %u blocks across %zu peers (window=%u, inflight=%u)\n",
nQueued, vEligiblePeers.size(), nWindow, nInFlight);
return nQueued;
}
} // namespace
//////////////////////////////////////////////////////////////////////////////
@@ -1322,6 +1407,48 @@ uint256 WantedByOrphan(const CBlock* pblockOrphan)
return pblockOrphan->hashPrevBlock;
}
// Evict excess orphan blocks when limit is exceeded
// Returns number of orphans evicted
unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans)
{
unsigned int nEvicted = 0;
while (mapOrphanBlocks.size() > nMaxOrphans)
{
// Evict a random orphan
uint256 randomhash = GetRandHash();
auto it = mapOrphanBlocks.lower_bound(randomhash);
if (it == mapOrphanBlocks.end())
it = mapOrphanBlocks.begin();
if (it == mapOrphanBlocks.end())
break; // No orphans to evict
CBlock* pblockEvict = it->second;
uint256 evictHash = it->first;
// Remove from by-prev index
for (auto range = mapOrphanBlocksByPrev.equal_range(pblockEvict->hashPrevBlock);
range.first != range.second; ++range.first)
{
if (range.first->second == pblockEvict) {
mapOrphanBlocksByPrev.erase(range.first);
break;
}
}
setStakeSeenOrphan.erase(pblockEvict->GetProofOfStake());
delete pblockEvict;
mapOrphanBlocks.erase(evictHash);
nEvicted++;
}
if (nEvicted > 0)
printf("LimitOrphanBlocks: evicted %u orphan(s), %u remain\n",
nEvicted, (unsigned int)mapOrphanBlocks.size());
return nEvicted;
}
// miner's coin base reward
int64_t GetProofOfWorkReward(int64_t nFees)
{
@@ -1415,9 +1542,13 @@ static unsigned int GetNextTargetRequired_(const CBlockIndex* pindexLast, bool f
return bnTargetLimit.GetCompact(); // genesis block
const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake);
if (pindexPrev == NULL)
return bnTargetLimit.GetCompact(); // no previous block of this type
if (pindexPrev->pprev == NULL)
return bnTargetLimit.GetCompact(); // first block
const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake);
if (pindexPrevPrev == NULL)
return bnTargetLimit.GetCompact(); // no second previous block of this type
if (pindexPrevPrev->pprev == NULL)
return bnTargetLimit.GetCompact(); // second block
@@ -1504,8 +1635,12 @@ void static InvalidChainFound(CBlockIndex* pindexNew)
uiInterface.NotifyBlocksChanged();
}
uint256 nBestInvalidBlockTrust = pindexNew->nChainTrust - pindexNew->pprev->nChainTrust;
uint256 nBestBlockTrust = pindexBest->nHeight != 0 ? (pindexBest->nChainTrust - pindexBest->pprev->nChainTrust) : pindexBest->nChainTrust;
uint256 nBestInvalidBlockTrust = pindexNew->pprev
? pindexNew->nChainTrust - pindexNew->pprev->nChainTrust
: pindexNew->nChainTrust;
uint256 nBestBlockTrust = (pindexBest && pindexBest->nHeight != 0 && pindexBest->pprev)
? (pindexBest->nChainTrust - pindexBest->pprev->nChainTrust)
: (pindexBest ? pindexBest->nChainTrust : uint256(0));
printf("InvalidChainFound: invalid block=%s height=%d trust=%s blocktrust=%" PRId64 " date=%s\n",
pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight,
@@ -1513,9 +1648,9 @@ void static InvalidChainFound(CBlockIndex* pindexNew)
DateTimeStrFormat("%x %H:%M:%S", pindexNew->GetBlockTime()).c_str());
printf("InvalidChainFound: current best=%s height=%d trust=%s blocktrust=%" PRId64 " date=%s\n",
hashBestChain.ToString().substr(0,20).c_str(), nBestHeight,
CBigNum(pindexBest->nChainTrust).ToString().c_str(),
pindexBest ? CBigNum(pindexBest->nChainTrust).ToString().c_str() : "0",
nBestBlockTrust.Get64(),
DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str());
pindexBest ? DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str() : "unknown");
}
@@ -2034,6 +2169,31 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
// Track pending UTXOs so later txs in the same block can find inputs.
if (fAssumeValid)
{
// Track money supply from input/output values
int64_t nTxValueOut = tx.GetValueOut();
nValueOut += nTxValueOut;
if (!tx.IsCoinBase())
{
int64_t nTxValueIn = 0;
for (const CTxIn& txin : tx.vin)
{
// Check in-block pending UTXOs first, then UTXO database
MapPrevTx::iterator it = mapPendingUtxos.find(txin.prevout);
if (it != mapPendingUtxos.end())
nTxValueIn += it->second.nValue;
else
{
CUtxoEntry utxo;
if (txdb.ReadUtxo(txin.prevout.hash, txin.prevout.n, utxo))
nTxValueIn += utxo.nValue;
}
}
nValueIn += nTxValueIn;
if (!tx.IsCoinStake())
nFees += nTxValueIn - nTxValueOut;
}
// Add outputs to pending UTXOs
for (unsigned int k = 0; k < tx.vout.size(); k++)
{
@@ -2295,7 +2455,15 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
{
printf("REORGANIZE\n");
printf("REORGANIZE: Switching chains\n");
printf(" Old tip: %s height %d trust %s\n",
pindexBest->GetBlockHash().ToString().substr(0,20).c_str(),
pindexBest->nHeight,
CBigNum(pindexBest->nChainTrust).ToString().c_str());
printf(" New tip: %s height %d trust %s\n",
pindexNew->GetBlockHash().ToString().substr(0,20).c_str(),
pindexNew->nHeight,
CBigNum(pindexNew->nChainTrust).ToString().c_str());
// Find the fork
CBlockIndex* pfork = pindexBest;
@@ -2322,8 +2490,17 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
vConnect.push_back(pindex);
reverse(vConnect.begin(), vConnect.end());
printf("REORGANIZE: Disconnect %" PRIszu " blocks; %s..%s\n", vDisconnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexBest->GetBlockHash().ToString().substr(0,20).c_str());
printf("REORGANIZE: Connect %" PRIszu " blocks; %s..%s\n", vConnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->GetBlockHash().ToString().substr(0,20).c_str());
printf("REORGANIZE: Fork point at height %d: %s\n",
pfork->nHeight,
pfork->GetBlockHash().ToString().substr(0,20).c_str());
printf("REORGANIZE: Disconnect %" PRIszu " blocks (heights %d..%d)\n",
vDisconnect.size(),
pfork->nHeight + 1,
pindexBest->nHeight);
printf("REORGANIZE: Connect %" PRIszu " blocks (heights %d..%d)\n",
vConnect.size(),
pfork->nHeight + 1,
pindexNew->nHeight);
// Disconnect shorter branch
vector<CTransaction> vResurrect;
@@ -2368,19 +2545,37 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
if (!txdb.TxnCommit())
return error("Reorganize() : TxnCommit failed");
// Disconnect shorter branch
// ======================================================================
// CRITICAL: All operations below this point must be in-memory only and
// should never fail. The DB transaction is committed, so we cannot abort.
// ======================================================================
// Disconnect shorter branch (in-memory only)
for (CBlockIndex* pindex : vDisconnect)
if (pindex->pprev)
pindex->pprev->pnext = NULL;
// Connect longer branch
// Connect longer branch (in-memory only)
for (CBlockIndex* pindex : vConnect)
if (pindex->pprev)
pindex->pprev->pnext = pindex;
// Remove disconnected PoS blocks from setStakeSeen so they don't
// block acceptance of valid blocks on the winning chain.
// This MUST happen after commit to maintain consistency.
for (CBlockIndex* pindex : vDisconnect)
if (pindex->IsProofOfStake())
setStakeSeen.erase(make_pair(pindex->prevoutStake, pindex->nStakeTime));
// Resurrect memory transactions that were in the disconnected branch
unsigned int nResurrected = 0;
for (CTransaction& tx : vResurrect)
tx.AcceptToMemoryPool(txdb, false);
{
if (tx.AcceptToMemoryPool(txdb, false))
nResurrected++;
}
if (nResurrected > 0)
printf("REORGANIZE: resurrected %u transactions to mempool\n", nResurrected);
// Delete redundant memory transactions that are in the connected branch
for (CTransaction& tx : vDelete) {
@@ -2388,7 +2583,8 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
mempool.removeConflicts(tx);
}
printf("REORGANIZE: done\n");
printf("REORGANIZE: done (fork at height %d, %zu disconnected, %zu connected)\n",
pfork->nHeight, vDisconnect.size(), vConnect.size());
return true;
}
@@ -2505,7 +2701,7 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
nTimeBestReceived = GetTime();
nTransactionsUpdated++;
uint256 nBestBlockTrust = pindexBest->nHeight != 0 ? (pindexBest->nChainTrust - pindexBest->pprev->nChainTrust) : pindexBest->nChainTrust;
uint256 nBestBlockTrust = (pindexBest->nHeight != 0 && pindexBest->pprev) ? (pindexBest->nChainTrust - pindexBest->pprev->nChainTrust) : pindexBest->nChainTrust;
// Log every 5000 blocks during sync, every block once caught up
if (nBestHeight % 5000 == 0 || !IsInitialBlockDownload())
@@ -2578,6 +2774,9 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
{
printf("*** Initial block download complete at height %d ***\n", nBestHeight);
// Trim orphan blocks to normal limit now that IBD is done
LimitOrphanBlocks(MAX_ORPHAN_BLOCKS);
// Update wallet best chain locator now that IBD is done
const CBlockLocator locator(pindexBest);
::SetBestChain(locator);
@@ -2765,15 +2964,25 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const u
// New best — keep the batch open so SetBestChain can add ConnectBlock
// writes to the same transaction, cutting the per-block commit count in half.
// v5.4: deterministic tiebreaker — when two chains have equal trust,
// all nodes agree on the one whose tip has the lower block hash.
// This prevents permanent forks from PoS blocks with identical difficulty.
//
// Chain selection rules:
// 1. Strictly greater trust always wins (normal case).
// 2. Equal trust: deterministic hash tiebreaker — lower tip hash wins.
// This ensures all nodes converge on the same chain even when two
// forks have identical cumulative difficulty (common in PoS).
// Rate-limited to one equal-trust reorg per 2 minutes — short enough
// for fast convergence but long enough to prevent oscillation on Tor.
bool fNewBest = false;
static int64_t nLastEqualTrustReorg = 0;
if (pindexNew->nChainTrust > nBestChainTrust)
fNewBest = true;
else if (pindexNew->nChainTrust == nBestChainTrust && pindexBest &&
pindexNew->GetBlockHash() < pindexBest->GetBlockHash())
pindexNew->GetBlockHash() < pindexBest->GetBlockHash() &&
GetTime() - nLastEqualTrustReorg > 2 * 60)
{
fNewBest = true;
nLastEqualTrustReorg = GetTime();
}
if (fNewBest)
{
@@ -2814,8 +3023,14 @@ bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot, bool fCheckSig) c
if (fCheckPOW && IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits))
return DoS(50, error("CheckBlock() : proof of work failed"));
// Check timestamp
if (GetBlockTime() > FutureDrift(GetAdjustedTime()))
// Check timestamp: reject blocks obviously too far in the future.
// Use a generous 15-minute window from the raw system clock.
// GetAdjustedTime() is NOT used here because it incorporates peer-reported
// time offsets that differ between Tor nodes, causing nondeterministic
// block rejection — the primary cause of persistent chain splits.
// The deterministic timestamp checks in AcceptBlock (median-time-past,
// prev-block-time with 3-min drift) still enforce tight rules.
if (GetBlockTime() > GetTime() + 15 * 60)
return error("CheckBlock() : block timestamp too far in the future");
// First transaction must be coinbase, the rest must not be
@@ -2975,14 +3190,20 @@ bool CBlock::AcceptBlock()
if (!AddToBlockIndex(nFile, nBlockPos, hashProofOfStake))
return error("AcceptBlock() : AddToBlockIndex failed");
// Relay inventory, but don't relay old inventory during initial block download
// Push new tip block directly to all peers. On a small Tor-only
// network the inv→getdata→block round-trip adds 1-2 seconds of latency
// per hop — enough for a competing staker to create a fork. Pushing
// the full block immediately cuts propagation to a single hop.
int nBlockEstimate = Checkpoints::GetTotalBlocksEstimate();
if (hashBestChain == hash)
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate))
pnode->PushInventory(CInv(MSG_BLOCK, hash));
{
pnode->PushMessage("block", *this);
pnode->AddInventoryKnown(CInv(MSG_BLOCK, hash));
}
}
return true;
@@ -3093,32 +3314,10 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
// Allow more orphans during IBD so out-of-order blocks from parallel
// downloads don't get evicted and re-requested.
unsigned int nMaxOrphans = IsInitialBlockDownload() ? MAX_ORPHAN_BLOCKS_IBD : MAX_ORPHAN_BLOCKS;
if (mapOrphanBlocks.size() > nMaxOrphans)
{
// Evict a random orphan
uint256 randomhash = GetRandHash();
auto it = mapOrphanBlocks.lower_bound(randomhash);
if (it == mapOrphanBlocks.end())
it = mapOrphanBlocks.begin();
CBlock* pblockEvict = it->second;
uint256 evictHash = it->first;
// Remove from by-prev index
for (auto range = mapOrphanBlocksByPrev.equal_range(pblockEvict->hashPrevBlock);
range.first != range.second; ++range.first)
{
if (range.first->second == pblockEvict) {
mapOrphanBlocksByPrev.erase(range.first);
break;
}
}
setStakeSeenOrphan.erase(pblockEvict->GetProofOfStake());
delete pblockEvict;
mapOrphanBlocks.erase(evictHash);
printf("ProcessBlock: orphan eviction, %u orphans remain\n", (unsigned int)mapOrphanBlocks.size());
}
LimitOrphanBlocks(nMaxOrphans);
// Ask this guy to fill in what we're missing
if (pfrom)
if (pfrom && pindexBest)
{
pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2));
// triangles: getblocks may not obtain the ancestor block rejected
@@ -3161,9 +3360,10 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
if (nBestHeight % 5000 == 0 || !IsInitialBlockDownload())
printf("ProcessBlock: ACCEPTED block %d\n", nBestHeight);
if (pfrom && hashBestHeaderSync != 0)
if (hashBestHeaderSync != 0)
{
const unsigned int nQueued = QueueHeaderSyncBlocks(pfrom, HEADER_DOWNLOAD_WINDOW);
// Use parallel queue to distribute across all peers
const unsigned int nQueued = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
if (nQueued > 0)
printf("IBD-DIAG: queued %u more blocks from header planner after accepting %s\n",
nQueued, hash.ToString().substr(0,20).c_str());
@@ -3714,10 +3914,6 @@ bool FastImportBlockFile()
}
pindexNew->nStakeModifierChecksum = GetStakeModifierChecksum(pindexNew);
// Money supply tracking
pindexNew->nMint = 0;
pindexNew->nMoneySupply = (pindexNew->pprev ? pindexNew->pprev->nMoneySupply : 0);
// PoS stake seen set
if (pindexNew->IsProofOfStake())
setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime));
@@ -3730,10 +3926,10 @@ bool FastImportBlockFile()
if (pindexNew->pprev)
pindexNew->pprev->pnext = pindexNew;
// Write block index to batch
txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
// Build tx index + UTXO entries
// Build tx index + UTXO entries, tracking money supply
int64_t nBlockValueIn = 0;
int64_t nBlockValueOut = 0;
int64_t nFees = 0;
unsigned int nTxPos = nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION)
- (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(block.vtx.size());
for (unsigned int i = 0; i < block.vtx.size(); i++)
@@ -3744,11 +3940,23 @@ bool FastImportBlockFile()
txdb.UpdateTxIndex(hashTx, CTxIndex(posThisTx, tx.vout.size()));
nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
// UTXO entries
int64_t nTxValueOut = tx.GetValueOut();
nBlockValueOut += nTxValueOut;
// UTXO entries — read input values before erasing for money supply
if (!tx.IsCoinBase())
{
int64_t nTxValueIn = 0;
for (const CTxIn& txin : tx.vin)
{
CUtxoEntry utxo;
if (txdb.ReadUtxo(txin.prevout.hash, txin.prevout.n, utxo))
nTxValueIn += utxo.nValue;
txdb.EraseUtxo(txin.prevout.hash, txin.prevout.n);
}
nBlockValueIn += nTxValueIn;
if (!tx.IsCoinStake())
nFees += nTxValueIn - nTxValueOut;
}
for (unsigned int k = 0; k < tx.vout.size(); k++)
{
@@ -3766,6 +3974,13 @@ bool FastImportBlockFile()
}
}
// Money supply tracking — matches ConnectBlock formula
pindexNew->nMint = nBlockValueOut - nBlockValueIn + nFees;
pindexNew->nMoneySupply = (pindexNew->pprev ? pindexNew->pprev->nMoneySupply : 0) + nBlockValueOut - nBlockValueIn;
// Write block index to batch
txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
// Update best chain
if (pindexNew->nChainTrust > nBestChainTrust)
{
@@ -4457,7 +4672,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
int nRequested = 0;
if (hashBestHeaderSync != 0)
nRequested = QueueHeaderSyncBlocks(pfrom, HEADER_DOWNLOAD_WINDOW);
nRequested = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
if (nNewHeaders > 0 || nRequested > 0)
printf("IBD-DIAG: accepted %d new headers, queued %d blocks from %zu headers (peer=%s bestHeader=%s)\n",
@@ -5099,6 +5314,20 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
pto->PushMessage("inv", vInv);
//
// Periodic chain-tip sync: every 45 seconds, ask each peer if they
// have blocks we don't. On a small Tor-only network, transient
// partitions can cause forks that persist silently — this ensures
// nodes discover the longer chain even without explicit announcement.
//
if (!IsInitialBlockDownload() && !pto->fClient && pindexBest &&
GetTime() - pto->nLastTipCheck > 45)
{
pto->nLastTipCheck = GetTime();
pto->pindexLastGetBlocksBegin = NULL; // reset dedup to force request
pto->PushGetBlocks(pindexBest, uint256(0));
}
//
// Stall detection: if we're still catching up and no new blocks for
// a while, re-request. Active during IBD (5s timeout) and also
+9 -4
View File
@@ -37,7 +37,7 @@ 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 = 2000;
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;
@@ -59,11 +59,15 @@ 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); }
inline int64_t PastDrift(int64_t nTime) { return PastDrift(nTime, nBestHeight); }
inline int64_t FutureDrift(int64_t nTime) { return FutureDrift(nTime, nBestHeight); }
// 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.
inline int64_t PastDrift(int64_t nTime) { return PastDrift(nTime, FORK_HEIGHT_V5_4); }
inline int64_t FutureDrift(int64_t nTime) { return FutureDrift(nTime, FORK_HEIGHT_V5_4); }
extern CScript COINBASE_FLAGS;
@@ -132,6 +136,7 @@ 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);
+1 -1
View File
@@ -413,7 +413,7 @@ void StakeMiner(CWallet *pwallet)
if (fTryToSync)
{
fTryToSync = false;
if (vNodes.size() < 1 || nBestHeight < GetNumBlocksOfPeers())
if (vNodes.size() < 2 || nBestHeight < GetNumBlocksOfPeers())
{
MilliSleep(60000);
continue;
+28 -7
View File
@@ -1403,7 +1403,32 @@ void ThreadOnionSeed(void* parg)
// This is the primary discovery mechanism — seeds.cryptographic-triangles.org
ThreadHTTPSeedFetch2(NULL);
printf("ThreadOnionSeed: seeding complete\n");
printf("ThreadOnionSeed: initial seeding complete\n");
// Periodic re-seeding: if the node becomes isolated (0 outbound peers),
// re-fetch the seed list. Check every 10 minutes, re-seed at most once
// per 30 minutes to avoid hammering the seed server.
int64_t nLastReseed = GetTime();
while (!fShutdown) {
for (int i = 0; i < 600 && !fShutdown; i++) // sleep 10 minutes
MilliSleep(1000);
if (fShutdown) break;
int nOutbound = 0;
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
if (!pnode->fInbound)
nOutbound++;
}
if (nOutbound == 0 && GetTime() - nLastReseed > 30 * 60) {
printf("ThreadOnionSeed: no outbound peers, re-seeding...\n");
ThreadHTTPSeedFetch2(NULL);
nLastReseed = GetTime();
}
}
}
@@ -1622,12 +1647,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)
+2
View File
@@ -272,6 +272,7 @@ public:
CBlockIndex* pindexLastGetHeadersBegin;
uint256 hashLastGetHeadersEnd;
int nStartingHeight;
int64_t nLastTipCheck; // last time we asked this peer for chain tip
// flood relay
std::vector<CAddress> vAddrToSend;
@@ -319,6 +320,7 @@ public:
pindexLastGetHeadersBegin = 0;
hashLastGetHeadersEnd = 0;
nStartingHeight = -1;
nLastTipCheck = 0;
fGetAddr = false;
nMisbehavior = 0;
hashCheckpointKnown = 0;
-6
View File
@@ -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"
+1 -1
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;
+209
View File
@@ -28,6 +28,9 @@ double GetDifficulty(const CBlockIndex* blockindex)
blockindex = GetLastBlockIndex(pindexBest, false);
}
if (blockindex == NULL)
return 1.0;
int nShift = (blockindex->nBits >> 24) & 0xff;
double dDiff =
@@ -360,6 +363,49 @@ Value gettxoutsetinfo(const Array& params, bool fHelp)
return obj;
}
Value recalculatesupply(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 0)
throw runtime_error(
"recalculatesupply\n"
"Recalculates the money supply by summing all unspent transaction outputs.\n"
"Updates the stored money supply value at the chain tip and persists it to disk.\n"
"Returns the old and new supply values for comparison.\n"
"\nWARNING: This modifies blockchain index state. Only use if money supply is incorrect.");
if (!pindexBest)
throw runtime_error("recalculatesupply: no best block");
int nUtxoCount = 0;
CTxDB txdb;
int64_t nCalculatedSupply = txdb.SumUtxoValues(nUtxoCount);
int64_t nOldSupply = pindexBest->nMoneySupply;
int64_t nDifference = nCalculatedSupply - nOldSupply;
// Sanity check: difference should be reasonable (not millions of TRI)
// Max supply is 2,222,222 TRI, so any difference > 1M TRI is suspicious
if (abs64(nDifference) > 1000000 * COIN)
throw runtime_error(strprintf(
"recalculatesupply: calculated supply differs by %s TRI - this is abnormal, refusing to update",
FormatMoney(abs64(nDifference)).c_str()));
// Update the chain tip's money supply
pindexBest->nMoneySupply = nCalculatedSupply;
// Persist to LevelDB
CTxDB txdbWrite;
if (!txdbWrite.WriteBlockIndex(CDiskBlockIndex(pindexBest)))
throw runtime_error("recalculatesupply: failed to write updated block index");
Object result;
result.push_back(Pair("height", (int)nBestHeight));
result.push_back(Pair("old_supply", ValueFromAmount(nOldSupply)));
result.push_back(Pair("new_supply", ValueFromAmount(nCalculatedSupply)));
result.push_back(Pair("difference", ValueFromAmount(nDifference)));
result.push_back(Pair("utxo_count", nUtxoCount));
return result;
}
// triangles: get information of sync-checkpoint
Value getcheckpoint(const Array& params, bool fHelp)
{
@@ -598,3 +644,166 @@ 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())
{
CTxDB txdb;
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 = NULL;
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.");
CTxDB txdb;
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;
}
+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;
}
+71
View File
@@ -2,6 +2,7 @@
// 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"
@@ -169,6 +170,76 @@ Value sendalert(const Array& params, bool fHelp)
return result;
}
Value addnode(const Array& params, bool fHelp)
{
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(
"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).");
string strNode = params[0].get_str();
// Tor-native: require .onion addresses
if (strNode.find(".onion") == string::npos)
throw runtime_error("Only .onion addresses are supported on this network.");
if (strCommand == "onetry")
{
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;
}
// 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)
{
if (fHelp || params.size() != 0)
+2 -1
View File
@@ -1944,7 +1944,8 @@ Value clearwallettransactions(const Array& params, bool fHelp)
if (datKey.get_data() == NULL || datValue.get_data() == NULL
|| ret != 0)
{
snprintf(cbuf, sizeof(cbuf), "wallet DB error %d, %s", ret, db_strerror(ret));
const char* dbErr = db_strerror(ret);
snprintf(cbuf, sizeof(cbuf), "wallet DB error %d, %s", ret, dbErr ? dbErr : "unknown");
throw runtime_error(cbuf);
};
+6
View File
@@ -248,6 +248,8 @@ static const CRPCCommand vRPCCommands[] =
{ "getblockcount", &getblockcount, true, false },
{ "getconnectioncount", &getconnectioncount, true, false },
{ "getpeerinfo", &getpeerinfo, true, false },
{ "addnode", &addnode, true, false },
{ "disconnectnode", &disconnectnode, true, false },
{ "getdifficulty", &getdifficulty, true, false },
{ "getblockheader", &getblockheader, true, false },
{ "getblockchaininfo", &getblockchaininfo, true, false },
@@ -312,6 +314,10 @@ static const CRPCCommand vRPCCommands[] =
{ "signrawtransaction", &signrawtransaction, false, false },
{ "sendrawtransaction", &sendrawtransaction, false, false },
{ "getcheckpoint", &getcheckpoint, true, false },
{ "getchaintips", &getchaintips, true, false },
{ "invalidateblock", &invalidateblock, false, false },
{ "reconsiderblock", &reconsiderblock, false, false },
{ "recalculatesupply", &recalculatesupply, false, false },
{ "reservebalance", &reservebalance, false, true},
{ "checkwallet", &checkwallet, false, true},
{ "repairwallet", &repairwallet, false, true},
+6
View File
@@ -148,6 +148,8 @@ extern json_spirit::Value getconnectioncount(const json_spirit::Array& params, b
extern json_spirit::Value getpeerinfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getnetworkinfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getseedlist(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value addnode(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value disconnectnode(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getwalletinfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value dumpwallet(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fHelp);
@@ -220,6 +222,10 @@ extern json_spirit::Value getblockhash(const json_spirit::Array& params, bool fH
extern json_spirit::Value getblock(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getblockbynumber(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getcheckpoint(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getchaintips(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value invalidateblock(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value reconsiderblock(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value recalculatesupply(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getaddressbalance(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getaddressutxos(const json_spirit::Array& params, bool fHelp);
+79 -1
View File
@@ -162,7 +162,9 @@ bool CTxDB::TxnCommit()
delete activeBatch;
activeBatch = NULL;
if (!status.ok()) {
printf("LevelDB batch commit failure: %s\n", status.ToString().c_str());
printf("ERROR: LevelDB batch commit failure: %s\n", status.ToString().c_str());
printf("ERROR: This may indicate disk full, corruption, or permissions issue.\n");
printf("ERROR: Chain state may be inconsistent - immediate investigation required!\n");
return false;
}
return true;
@@ -613,6 +615,50 @@ bool CTxDB::LoadBlockIndex()
hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str(),
DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str());
// Re-evaluate best chain: scan for competing tips with equal or greater trust.
// This fixes nodes stuck on the wrong fork after consensus rule changes.
{
CBlockIndex* pindexBetter = NULL;
for (const auto& item : mapBlockIndex)
{
CBlockIndex* pindex = item.second;
if (pindex == pindexBest)
continue;
if (pindex->nChainTrust > nBestChainTrust)
{
pindexBetter = pindex;
break;
}
if (pindex->nChainTrust == nBestChainTrust &&
pindex->GetBlockHash() < pindexBest->GetBlockHash())
{
if (!pindexBetter || pindex->GetBlockHash() < pindexBetter->GetBlockHash())
pindexBetter = pindex;
}
}
if (pindexBetter)
{
printf("LoadBlockIndex(): found better chain tip %s at height %d (trust %s vs %s)\n",
pindexBetter->GetBlockHash().ToString().substr(0,20).c_str(),
pindexBetter->nHeight,
CBigNum(pindexBetter->nChainTrust).ToString().c_str(),
CBigNum(nBestChainTrust).ToString().c_str());
CBlock block;
if (block.ReadFromDisk(pindexBetter))
{
CTxDB txdb2;
if (block.SetBestChain(txdb2, pindexBetter))
{
hashBestChain = pindexBetter->GetBlockHash();
pindexBest = pindexBetter;
nBestHeight = pindexBetter->nHeight;
nBestChainTrust = pindexBetter->nChainTrust;
printf("LoadBlockIndex(): switched to better chain tip\n");
}
}
}
}
// triangles: load hashSyncCheckpoint (best-effort, non-fatal)
if (!ReadSyncCheckpoint(Checkpoints::hashSyncCheckpoint))
printf("LoadBlockIndex(): no sync checkpoint in DB, using default\n");
@@ -860,3 +906,35 @@ bool CTxDB::HaveUtxo(const uint256& hash, unsigned int n)
return false;
}
int64_t CTxDB::SumUtxoValues(int& nCount)
{
nCount = 0;
int64_t nTotal = 0;
// Seek to the start of UTXO entries (key prefix "u")
CDataStream ssKeyPrefix(SER_DISK, CLIENT_VERSION);
ssKeyPrefix << make_pair(string("u"), make_pair(uint256(0), (unsigned int)0));
std::string strPrefixBegin = ssKeyPrefix.str();
leveldb::Iterator* it = pdb->NewIterator(leveldb::ReadOptions());
for (it->Seek(strPrefixBegin); it->Valid(); it->Next())
{
// Check key prefix is still "u"
CDataStream ssKey(it->key().data(), it->key().data() + it->key().size(), SER_DISK, CLIENT_VERSION);
std::string strKeyType;
ssKey >> strKeyType;
if (strKeyType != "u")
break;
// Deserialize the UTXO entry and sum the value
CDataStream ssValue(it->value().data(), it->value().data() + it->value().size(), SER_DISK, CLIENT_VERSION);
CUtxoEntry entry;
ssValue >> entry;
nTotal += entry.nValue;
nCount++;
}
delete it;
return nTotal;
}
+1
View File
@@ -236,6 +236,7 @@ public:
bool WriteUtxo(const uint256& hash, unsigned int n, const CUtxoEntry& entry);
bool EraseUtxo(const uint256& hash, unsigned int n);
bool HaveUtxo(const uint256& hash, unsigned int n);
int64_t SumUtxoValues(int& nCount);
private:
bool LoadBlockIndexGuts();
+4 -4
View File
@@ -41,16 +41,16 @@ const std::string CLIENT_NAME("Cryptographic Triangles");
#endif
#define BUILD_DESC_FROM_COMMIT(maj,min,rev,build,commit) \
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) "" commit
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "" commit
#define BUILD_DESC_FROM_UNKNOWN(maj,min,rev,build) \
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) ""
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) ""
#ifndef BUILD_DESC
# ifdef GIT_COMMIT_ID
# define BUILD_DESC BUILD_DESC_FROM_COMMIT(DISPLAY_VERSION_MAJOR, DISPLAY_VERSION_MINOR, DISPLAY_VERSION_REVISION, DISPLAY_VERSION_BUILD, GIT_COMMIT_ID)
# define BUILD_DESC BUILD_DESC_FROM_COMMIT(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD, GIT_COMMIT_ID)
# else
# define BUILD_DESC BUILD_DESC_FROM_UNKNOWN(DISPLAY_VERSION_MAJOR, DISPLAY_VERSION_MINOR, DISPLAY_VERSION_REVISION, DISPLAY_VERSION_BUILD)
# define BUILD_DESC BUILD_DESC_FROM_UNKNOWN(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD)
# endif
#endif
-5
View File
@@ -51,9 +51,4 @@ static const int BIP0031_VERSION = 60000;
// "mempool" command, enhanced "getdata" behavior starts with this version:
static const int MEMPOOL_GD_VERSION = 60002;
#define DISPLAY_VERSION_MAJOR 5
#define DISPLAY_VERSION_MINOR 7
#define DISPLAY_VERSION_REVISION 6
#define DISPLAY_VERSION_BUILD 0
#endif
+38
View File
@@ -16,6 +16,40 @@ namespace fs = boost::filesystem;
static uint64_t nAccountingEntryNumber = 0;
extern bool fWalletUnlockStakingOnly;
//
// Auto-backup wallet before flush/rewrite operations.
// Copies wallet.dat to wallet.dat.auto.bak if the backup is older than the wallet.
// Returns true if backup was created or already up to date.
//
bool AutoBackupWallet(const fs::path& walletPath)
{
fs::path backupPath = walletPath.string() + ".auto.bak";
try {
// Only back up if wallet exists and is non-trivial (>1KB)
if (!fs::exists(walletPath))
return true;
uintmax_t walletSize = fs::file_size(walletPath);
if (walletSize < 1024) {
printf("AutoBackupWallet: wallet.dat is only %llu bytes (possibly corrupt), skipping auto-backup\n",
(unsigned long long)walletSize);
return false;
}
// Skip if backup exists and is same size (already backed up this version)
if (fs::exists(backupPath)) {
uintmax_t backupSize = fs::file_size(backupPath);
if (backupSize == walletSize)
return true;
}
fs::copy_file(walletPath, backupPath, fs::copy_options::overwrite_existing);
printf("AutoBackupWallet: backed up wallet.dat (%llu bytes) to wallet.dat.auto.bak\n",
(unsigned long long)walletSize);
return true;
} catch (const fs::filesystem_error& e) {
printf("AutoBackupWallet: failed - %s\n", e.what());
return false;
}
}
//
// CWalletDB
//
@@ -580,6 +614,10 @@ void ThreadFlushWalletDB(void* parg)
nLastFlushed = nWalletDBUpdated;
int64_t nStart = GetTimeMillis();
// Auto-backup before flush (protects against corruption)
fs::path walletPath = GetDataDir() / strFile;
AutoBackupWallet(walletPath);
// Flush wallet.dat so it's self contained
bitdb.CloseDb(strFile);
bitdb.CheckpointLSN(strFile);
+36
View File
@@ -0,0 +1,36 @@
## TRI Node Upgrade to v5.8.0 - April 14, 2026
This document outlines the process and results of upgrading the TRI network nodes to version 5.8.0.
### Initial State
- **DNS2:** `v5.7.9` @ block `2,203,611`
- **DNS3:** `v5.7.5` @ block `2,204,954`
- **Contabo Seeds:** `v5.7.9` @ block `2,203,594`
Nodes were on multiple versions and forks.
### Upgrade Process
1. **Version Confirmation:** Verified `v5.8.0` was available on GitHub.
2. **Upgrades:**
- DNS2 upgraded to `v5.8.0` via `dpkg`.
- DNS3 upgraded to `v5.8.0` via `dpkg`.
- Contabo seeds (`tri-seed-1` to `4`) upgraded to `v5.8.0` via `dpkg` inside their containers.
3. **Chain Reset:** To resolve forks, the chain data (blocks, chainstate, peers) was wiped on DNS2 and all Contabo seeds. Wallets and configs were preserved. DNS3 was left as the canonical chain source.
### Current Status
- All nodes are now running `v5.8.0`.
- Nodes are currently re-syncing to the canonical chain. Monitoring is in progress.
### DNS2 Wallet Corruption and Recovery
- **Symptom:** `triangles.service` on DNS2 was in a crash loop. Logs showed a recurring `CDB() : can't open database file wallet.dat, error -30973` error.
- **Diagnosis:** `wallet.dat` file was corrupted.
- **Recovery:**
1. The corrupted wallet was moved to `wallet.dat.corrupted` for safety.
2. The latest wallet backup (`dns2-wallet_20260414_031501.dat`) was restored from Dropbox.
3. The `triangles.service` was restarted.
This restored the wallet to a healthy state.