Compare commits

...

5 Commits

Author SHA1 Message Date
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
23 changed files with 954 additions and 168 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ if(POLICY CMP0167)
endif()
project(Triangles
VERSION 5.7.8.0
VERSION 5.8.1.0
DESCRIPTION "Cryptographic Triangles Wallet"
LANGUAGES C CXX
)
+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.
+46 -28
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,11 +24,7 @@ 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`
@@ -40,32 +36,46 @@ The Triangles network is live with seed nodes operating on both clearnet and Tor
## 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 +86,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 +123,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 +173,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 +217,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
+86 -61
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,18 @@
#include <cstring>
#include <cstdlib>
#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/socket.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,26 +40,48 @@ 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
}
}
bool DownloadFile(const std::string& host, const std::string& urlPath,
const fs::path& destPath,
ProgressCallback progressFn,
std::string& strError)
{
try {
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
boost::system::error_code resolve_ec;
tcp::resolver::results_type endpoints =
resolver.resolve(host, std::to_string(PORT), resolve_ec);
if (resolve_ec) {
strError = "Cannot resolve host: " + host;
// Connect through Tor SOCKS proxy (ConnectSocketByName respects SetProxy)
SOCKET hSocket = INVALID_SOCKET;
CService addr;
if (!ConnectSocketByName(addr, hSocket, host.c_str(), PORT, 30)) {
strError = "Cannot connect to " + host + " (check Tor proxy)";
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"
@@ -60,85 +89,81 @@ bool DownloadFile(const std::string& host, const std::string& urlPath,
"Connection: close\r\n"
"User-Agent: Triangles\r\n"
"\r\n";
boost::asio::write(socket, boost::asio::buffer(request));
if (!SendAll(hSocket, request.data(), request.size())) {
closesocket(hSocket);
strError = "Failed to send request to " + host;
return false;
}
// Read response headers
boost::asio::streambuf response_buf;
boost::asio::read_until(socket, response_buf, "\r\n\r\n");
std::string headerData;
if (!RecvUntil(hSocket, headerData, "\r\n\r\n")) {
closesocket(hSocket);
strError = "Failed to read HTTP headers from " + host;
return false;
}
std::istream response_stream(&response_buf);
// Parse status line
std::string http_version;
// Parse status code from "HTTP/1.x NNN ..."
unsigned int status_code = 0;
response_stream >> http_version >> status_code;
std::string status_message;
std::getline(response_stream, status_message);
size_t sp = headerData.find(' ');
if (sp != std::string::npos)
status_code = atoi(headerData.c_str() + sp + 1);
if (status_code != 200) {
closesocket(hSocket);
strError = "HTTP error " + std::to_string(status_code) + " for " + urlPath;
return false;
}
// Parse headers for Content-Length
// Parse 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));
}
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) {
-1
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;
+2 -2
View File
@@ -7,8 +7,8 @@
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 5
#define CLIENT_VERSION_MINOR 7
#define CLIENT_VERSION_REVISION 8
#define CLIENT_VERSION_MINOR 8
#define CLIENT_VERSION_REVISION 1
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
-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());
+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);
}
}
+79 -28
View File
@@ -247,7 +247,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);
@@ -1504,8 +1504,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 +1517,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 +2038,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++)
{
@@ -2511,7 +2540,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())
@@ -2774,21 +2803,22 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const u
//
// Chain selection rules:
// 1. Strictly greater trust always wins (normal case).
// 2. Equal trust with shallow fork (parent in main chain): use
// deterministic hash tiebreaker — lower tip hash wins. This
// resolves single-block PoS races where two stakers find valid
// blocks at the same height with identical difficulty.
// 3. Equal trust with deep fork (parent NOT in main chain): do NOT
// reorg. Without this rule, Tor-latency-induced multi-block
// forks cause nodes to oscillate between competing chains of
// similar trust, preventing convergence.
// 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 10 minutes to prevent
// oscillation from Tor-latency-induced competing announcements.
bool fNewBest = false;
static int64_t nLastEqualTrustReorg = 0;
if (pindexNew->nChainTrust > nBestChainTrust)
fNewBest = true;
else if (pindexNew->nChainTrust == nBestChainTrust && pindexBest &&
pindexNew->pprev && pindexNew->pprev->IsInMainChain() &&
pindexNew->GetBlockHash() < pindexBest->GetBlockHash())
pindexNew->GetBlockHash() < pindexBest->GetBlockHash() &&
GetTime() - nLastEqualTrustReorg > 10 * 60)
{
fNewBest = true;
nLastEqualTrustReorg = GetTime();
}
if (fNewBest)
{
@@ -2829,8 +2859,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
@@ -3133,7 +3169,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
}
// 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
@@ -3729,10 +3765,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));
@@ -3745,10 +3777,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++)
@@ -3759,11 +3791,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++)
{
@@ -3781,6 +3825,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)
{
+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)
-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;
+163
View File
@@ -598,3 +598,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;
}
+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);
};
+5
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,9 @@ 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 },
{ "reservebalance", &reservebalance, false, true},
{ "checkwallet", &checkwallet, false, true},
{ "repairwallet", &repairwallet, false, true},
+5
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,9 @@ 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 getaddressbalance(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getaddressutxos(const json_spirit::Array& params, bool fHelp);
+44
View File
@@ -613,6 +613,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");
+2 -2
View File
@@ -48,9 +48,9 @@ const std::string CLIENT_NAME("Cryptographic Triangles");
#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
+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.