Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6eb25d6b25 | |||
| cd7b68f7cb | |||
| a0e8e74d0d | |||
| 7d62e34868 | |||
| b0e9ca334f | |||
| 029f5a4bfc | |||
| 0d6e143398 | |||
| 310a2b7371 | |||
| 9acff4bb43 | |||
| 97dbc13b62 | |||
| edf029e403 | |||
| b41d1be128 | |||
| 94df26a0e0 | |||
| d10ca379a7 | |||
| f5c0f53377 | |||
| ada278cb9f | |||
| 3579f98033 | |||
| f5a0bf1727 | |||
| 47a5ec1e38 | |||
| 56351ffb89 | |||
| 334b525fe6 | |||
| 76ec2da20d | |||
| 1e276da344 | |||
| 412ca94a25 | |||
| 23dc7992e5 | |||
| 46f719162b | |||
| e1a3eae0a3 | |||
| 57aaa1dcc6 | |||
| 48d84d40c5 | |||
| aa1251f4fa | |||
| d9deaf509b | |||
| 1b416ae704 | |||
| 6a124cb411 | |||
| 5b21f7cc1f | |||
| ed1ae79822 | |||
| f7eec5c138 | |||
| d6839164dd | |||
| f950eb58ad | |||
| 26276ef92b | |||
| b339ede17a | |||
| 62f7d6457c | |||
| b857257516 | |||
| 012bc344f5 | |||
| 0b8b693d7d | |||
| 74b11e7404 | |||
| 585ccd4a07 | |||
| db77184277 | |||
| 2413983dae | |||
| ca8baab501 | |||
| 2c4c8370df | |||
| bb4414804b | |||
| 050558435f | |||
| 35c4a2c823 | |||
| 89dad5818f | |||
| 999ffea314 | |||
| 098f27368f | |||
| 3dfecf5cb7 | |||
| cf4851bede | |||
| 2c3a2f983c | |||
| fbd498ad8e | |||
| 999726730b | |||
| d903ef2fc7 | |||
| 35369995fb | |||
| 4be0101f2d | |||
| 7c79dc5ae8 | |||
| 2f9841b0e3 | |||
| e7e2d8443e | |||
| 352dda5645 | |||
| daf47d2fab | |||
| 64e132b34b | |||
| 7ca970d998 | |||
| c2e5cf4330 | |||
| 6c5ce18459 | |||
| 91e026d7ec | |||
| 1011cf84fe | |||
| ad0088ef3a | |||
| 1f0b83f893 | |||
| 4605cb4b70 | |||
| 7b021a65c0 | |||
| 19ea33b706 | |||
| 37b69f45ea | |||
| 8203f97eeb | |||
| 3e19c1b232 | |||
| b34f7e5ebd | |||
| fea90d1f0a | |||
| 96bf97a3b3 | |||
| 3fdccf2b14 | |||
| 0b53c21eaf | |||
| 2c77ebb122 | |||
| 2b79d8a6f9 | |||
| a81570499b | |||
| 853efe3ad9 | |||
| 8cab86518c | |||
| 9a3b643a5a | |||
| 40d281c1ff | |||
| dd72957646 | |||
| 97577a677c | |||
| cec7ca2e2e | |||
| a82bf999d2 | |||
| 52203b6003 | |||
| 76775cc697 | |||
| 1911724373 | |||
| 790cbd1cea | |||
| 91203c4ef4 | |||
| 2e8f3f5194 | |||
| a3b479d954 | |||
| 9ca61bef47 | |||
| 2e4bca9493 | |||
| f54d456920 | |||
| 07b90a53de | |||
| 9f69ead715 | |||
| 3be4d18b81 | |||
| b7b9c13bfa | |||
| b6e0fc62a9 | |||
| 420630168b | |||
| 5f599a72da | |||
| c475f38b85 | |||
| d655d9ed70 | |||
| efb4455fa5 | |||
| e4d519711f | |||
| 5799125d5a | |||
| 3a9d845e94 | |||
| 6779662ec1 | |||
| 3fd9fe70eb | |||
| 1abe33c482 | |||
| c74d0dc0ee | |||
| 918e5bf5a2 | |||
| 5ea26a94ff | |||
| dbe22a8383 | |||
| 67a5d19ec1 | |||
| f07f7f902a | |||
| 158b2bcd2d | |||
| 1ede9babf6 | |||
| 104d496e77 | |||
| 76d128917a |
@@ -62,12 +62,16 @@ jobs:
|
||||
mingw-w64-x86_64-miniupnpc
|
||||
mingw-w64-x86_64-zlib
|
||||
|
||||
- name: Set VERSION from source
|
||||
- name: Set VERSION
|
||||
run: |
|
||||
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
|
||||
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
|
||||
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
|
||||
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
||||
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
||||
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
||||
else
|
||||
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
|
||||
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
|
||||
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
|
||||
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
@@ -143,20 +147,21 @@ jobs:
|
||||
- name: Download Tor
|
||||
shell: powershell
|
||||
run: |
|
||||
$TOR_VERSION = "15.0.8"
|
||||
$TOR_URL = "https://dist.torproject.org/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz"
|
||||
$TOR_VERSION = "15.0.9"
|
||||
$TOR_URL = "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz"
|
||||
Invoke-WebRequest -Uri $TOR_URL -OutFile tor-bundle.tar.gz
|
||||
New-Item -ItemType Directory -Path tor-extract -Force
|
||||
tar -xzf tor-bundle.tar.gz -C tor-extract
|
||||
New-Item -ItemType Directory -Path tor-files -Force
|
||||
Copy-Item tor-extract/tor/tor.exe tor-files/
|
||||
Copy-Item tor-extract/tor/tor-gencert.exe tor-files/ -ErrorAction SilentlyContinue
|
||||
Copy-Item -Recurse tor-extract/tor/* tor-files/
|
||||
if (Test-Path tor-extract/tor/pluggable_transports) {
|
||||
Copy-Item -Recurse tor-extract/tor/pluggable_transports tor-files/
|
||||
Copy-Item -Recurse tor-extract/tor/pluggable_transports tor-files/pluggable_transports -Force
|
||||
}
|
||||
if (Test-Path tor-extract/data) {
|
||||
Copy-Item -Recurse tor-extract/data tor-files/data
|
||||
}
|
||||
Write-Host "Bundled Tor runtime files:"
|
||||
Get-ChildItem -Recurse tor-files | Select-Object FullName
|
||||
|
||||
- name: Install NSIS via MSYS2
|
||||
run: pacman -S --noconfirm mingw-w64-x86_64-nsis
|
||||
@@ -232,11 +237,11 @@ jobs:
|
||||
- name: Bundle Tor for daemon
|
||||
shell: powershell
|
||||
run: |
|
||||
$TOR_VERSION = "15.0.8"
|
||||
Invoke-WebRequest -Uri "https://dist.torproject.org/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz" -OutFile tor-bundle.tar.gz
|
||||
$TOR_VERSION = "15.0.9"
|
||||
Invoke-WebRequest -Uri "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz" -OutFile tor-bundle.tar.gz
|
||||
New-Item -ItemType Directory -Path tor-extract -Force
|
||||
tar -xzf tor-bundle.tar.gz -C tor-extract
|
||||
Copy-Item tor-extract/tor/tor.exe daemon-dist/tor/
|
||||
Copy-Item -Recurse tor-extract/tor/* daemon-dist/tor/
|
||||
if (Test-Path tor-extract/data) {
|
||||
Copy-Item -Recurse tor-extract/data daemon-dist/tor/data
|
||||
}
|
||||
@@ -252,12 +257,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set VERSION from source
|
||||
- name: Set VERSION
|
||||
run: |
|
||||
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
|
||||
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
|
||||
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
|
||||
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
||||
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
||||
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
||||
else
|
||||
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
|
||||
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
|
||||
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
|
||||
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -284,8 +293,8 @@ jobs:
|
||||
|
||||
- name: Build .deb package (fully self-contained)
|
||||
run: |
|
||||
TOR_VERSION="15.0.8"
|
||||
curl -sL "https://dist.torproject.org/torbrowser/${TOR_VERSION}/tor-expert-bundle-linux-x86_64-${TOR_VERSION}.tar.gz" -o tor-bundle.tar.gz
|
||||
TOR_VERSION="15.0.9"
|
||||
curl -sL "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-linux-x86_64-${TOR_VERSION}.tar.gz" -o tor-bundle.tar.gz
|
||||
mkdir -p tor-extract && tar -xzf tor-bundle.tar.gz -C tor-extract
|
||||
|
||||
PKG="cryptographic-triangles_${VERSION}_amd64"
|
||||
@@ -365,12 +374,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set VERSION from source
|
||||
- name: Set VERSION
|
||||
run: |
|
||||
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
|
||||
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
|
||||
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
|
||||
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
||||
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
||||
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
||||
else
|
||||
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
|
||||
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
|
||||
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
|
||||
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -396,8 +409,8 @@ jobs:
|
||||
|
||||
- name: Build .deb package (fully self-contained)
|
||||
run: |
|
||||
TOR_VERSION="15.0.8"
|
||||
curl -sL "https://dist.torproject.org/torbrowser/${TOR_VERSION}/tor-expert-bundle-linux-x86_64-${TOR_VERSION}.tar.gz" -o tor-bundle.tar.gz
|
||||
TOR_VERSION="15.0.9"
|
||||
curl -sL "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-linux-x86_64-${TOR_VERSION}.tar.gz" -o tor-bundle.tar.gz
|
||||
mkdir -p tor-extract && tar -xzf tor-bundle.tar.gz -C tor-extract
|
||||
|
||||
PKG="cryptographic-triangles-daemon_${VERSION}_amd64"
|
||||
@@ -491,12 +504,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set VERSION from source
|
||||
- name: Set VERSION
|
||||
run: |
|
||||
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
|
||||
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
|
||||
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
|
||||
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
||||
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
||||
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
|
||||
else
|
||||
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
|
||||
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
|
||||
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
|
||||
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -566,8 +583,8 @@ jobs:
|
||||
|
||||
- name: Bundle Tor into app
|
||||
run: |
|
||||
TOR_VERSION="15.0.8"
|
||||
curl -sL "https://dist.torproject.org/torbrowser/${TOR_VERSION}/tor-expert-bundle-macos-aarch64-${TOR_VERSION}.tar.gz" -o tor-bundle.tar.gz
|
||||
TOR_VERSION="15.0.9"
|
||||
curl -sL "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-macos-aarch64-${TOR_VERSION}.tar.gz" -o tor-bundle.tar.gz
|
||||
mkdir -p tor-extract && tar -xzf tor-bundle.tar.gz -C tor-extract
|
||||
APP=$(find build/bin -name "*.app" -maxdepth 1 | head -1)
|
||||
mkdir -p "$APP/Contents/MacOS/tor"
|
||||
@@ -637,8 +654,9 @@ jobs:
|
||||
- name: Dispatch tri-pi ARM64 build
|
||||
run: |
|
||||
curl -f -X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token ${{ secrets.TRIPI_BUILD_TOKEN }}" \
|
||||
https://api.github.com/repos/SamiAhmed7777/tri-pi/actions/workflows/build-arm64.yml/dispatches \
|
||||
-d '{"ref":"main","inputs":{"version":"${{ github.ref_name }}"}}'
|
||||
echo "Triggered tri-pi build for ${{ github.ref_name }}"
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.TRIPI_BUILD_TOKEN }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/SamiAhmed7777/tri-pi/dispatches \
|
||||
-d '{"event_type":"new-release","client_payload":{"version":"${{ github.ref_name }}","source_repo":"SamiAhmed7777/triangles_v5"}}'
|
||||
echo "Triggered tri-pi repository_dispatch for ${{ github.ref_name }}"
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ if(POLICY CMP0167)
|
||||
endif()
|
||||
|
||||
project(Triangles
|
||||
VERSION 5.7.5.0
|
||||
VERSION 5.8.1.0
|
||||
DESCRIPTION "Cryptographic Triangles Wallet"
|
||||
LANGUAGES C CXX
|
||||
)
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ FROM ubuntu:22.04
|
||||
|
||||
LABEL maintainer="Cryptographic Triangles Team"
|
||||
LABEL description="Cryptographic Triangles (TRI) headless daemon"
|
||||
LABEL version="5.6.0"
|
||||
LABEL version="5.7.6"
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
@@ -19,7 +19,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
tor \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG VERSION=5.6.0
|
||||
ARG VERSION=5.7.6
|
||||
RUN curl -L -o /usr/local/bin/trianglesd \
|
||||
https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/Cryptographic-Triangles-v${VERSION}-linux-x64-daemon \
|
||||
&& chmod +x /usr/local/bin/trianglesd
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
@@ -3,7 +3,7 @@
|
||||
# Run on a Linux x64 system with appimagetool installed
|
||||
set -e
|
||||
|
||||
VERSION="5.6.0"
|
||||
VERSION="5.7.6"
|
||||
APPDIR="Triangles-x86_64.AppDir"
|
||||
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Run from the packaging/debian directory
|
||||
set -e
|
||||
|
||||
VERSION="5.6.0"
|
||||
VERSION="5.7.6"
|
||||
PKGDIR="triangles_${VERSION}-1_amd64"
|
||||
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ FROM ubuntu:22.04
|
||||
|
||||
LABEL maintainer="Cryptographic Triangles Team"
|
||||
LABEL description="Cryptographic Triangles (TRI) headless daemon"
|
||||
LABEL version="5.6.0"
|
||||
LABEL version="5.7.6"
|
||||
|
||||
ARG VERSION=5.6.0
|
||||
ARG VERSION=5.7.6
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.8"
|
||||
services:
|
||||
trianglesd:
|
||||
build: .
|
||||
image: cryptographic-triangles/trianglesd:5.6.0
|
||||
image: cryptographic-triangles/trianglesd:5.7.6
|
||||
container_name: trianglesd
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
||||
@@ -25,7 +25,7 @@ modules:
|
||||
- install -Dm644 org.cryptographic_triangles.TrianglesQt.metainfo.xml /app/share/metainfo/org.cryptographic_triangles.TrianglesQt.metainfo.xml
|
||||
sources:
|
||||
- type: file
|
||||
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-v5.6.0-linux-x64-qt
|
||||
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.7.6/Cryptographic-Triangles-v5.7.6-linux-x64-qt
|
||||
sha256: ed220eb8d0b403f62cdac28988541fd1a27864491e233216f9c00a4c2537b4a3
|
||||
dest-filename: triangles-qt-linux
|
||||
- type: file
|
||||
@@ -55,6 +55,6 @@ modules:
|
||||
- install -Dm755 trianglesd-linux /app/bin/trianglesd
|
||||
sources:
|
||||
- type: file
|
||||
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-v5.6.0-linux-x64-daemon
|
||||
url: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.7.6/Cryptographic-Triangles-v5.7.6-linux-x64-daemon
|
||||
sha256: 4d2ab25d61127d6aff3e6f3069556d04f4b823f8849e97629c12871ad4779517
|
||||
dest-filename: trianglesd-linux
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# Install build tools: sudo dnf install rpm-build rpmdevtools
|
||||
set -e
|
||||
|
||||
VERSION="5.6.0"
|
||||
VERSION="5.7.6"
|
||||
RELEASE_URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}"
|
||||
|
||||
echo "Building RPM for Triangles v${VERSION}..."
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: triangles
|
||||
Version: 5.6.0
|
||||
Version: 5.7.6
|
||||
Release: 1%{?dist}
|
||||
Summary: Cryptographic Triangles (TRI) cryptocurrency wallet
|
||||
License: MIT
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"version": "5.6.0",
|
||||
"version": "5.7.6",
|
||||
"description": "Cryptographic Triangles (TRI) cryptocurrency wallet with PoS staking and encrypted messaging",
|
||||
"homepage": "https://cryptographic-triangles.org",
|
||||
"license": "MIT",
|
||||
"architecture": {
|
||||
"64bit": {
|
||||
"url": "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-5.6.0-win-x64.zip",
|
||||
"url": "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.7.6/Cryptographic-Triangles-5.7.6-win-x64.zip",
|
||||
"hash": "6f002a669a7e92aaf3d8dd7b1ae80f06a086c99a15ca05cf107665009ffc06b7"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PackageIdentifier: CryptographicTriangles.TrianglesQt
|
||||
PackageVersion: 5.6.0
|
||||
PackageVersion: 5.7.6
|
||||
PackageLocale: en-US
|
||||
Publisher: Cryptographic Triangles
|
||||
PublisherUrl: https://cryptographic-triangles.org
|
||||
@@ -27,7 +27,7 @@ Installers:
|
||||
- RelativeFilePath: triangles-qt.exe
|
||||
PortableCommandAlias: triangles-qt
|
||||
ArchiveBinariesDependOnPath: true
|
||||
InstallerUrl: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-5.6.0-win-x64.zip
|
||||
InstallerUrl: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.7.6/Cryptographic-Triangles-5.7.6-win-x64.zip
|
||||
InstallerSha256: 6F002A669A7E92AAF3D8DD7B1AE80F06A086C99A15CA05CF107665009FFC06B7
|
||||
ManifestType: singleton
|
||||
ManifestVersion: 1.6.0
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
name: triangles
|
||||
base: core22
|
||||
version: '5.6.0'
|
||||
version: '5.7.6'
|
||||
summary: Cryptographic Triangles (TRI) cryptocurrency wallet
|
||||
description: |
|
||||
Privacy-focused cryptocurrency featuring Proof-of-Stake consensus,
|
||||
@@ -51,10 +51,10 @@ apps:
|
||||
parts:
|
||||
triangles:
|
||||
plugin: dump
|
||||
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-v5.6.0-linux-x64-qt
|
||||
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.7.6/Cryptographic-Triangles-v5.7.6-linux-x64-qt
|
||||
source-type: file
|
||||
organize:
|
||||
Cryptographic-Triangles-v5.6.0-linux-x64-qt: bin/triangles-qt
|
||||
Cryptographic-Triangles-v5.7.6-linux-x64-qt: bin/triangles-qt
|
||||
stage-packages:
|
||||
- libqt5widgets5
|
||||
- libqt5gui5
|
||||
@@ -73,10 +73,10 @@ parts:
|
||||
|
||||
trianglesd:
|
||||
plugin: dump
|
||||
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.6.0/Cryptographic-Triangles-v5.6.0-linux-x64-daemon
|
||||
source: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v5.7.6/Cryptographic-Triangles-v5.7.6-linux-x64-daemon
|
||||
source-type: file
|
||||
organize:
|
||||
Cryptographic-Triangles-v5.6.0-linux-x64-daemon: bin/trianglesd
|
||||
Cryptographic-Triangles-v5.7.6-linux-x64-daemon: bin/trianglesd
|
||||
|
||||
desktop-entry:
|
||||
plugin: dump
|
||||
|
||||
+87
-62
@@ -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) {
|
||||
@@ -434,7 +459,7 @@ bool DownloadBootstrap(const std::string& host,
|
||||
|
||||
// Try downloading bootstrap.tar.gz first
|
||||
fs::path tmpTarGz = dataDir / "bootstrap.tar.gz.tmp";
|
||||
std::string tarUrl = std::string(BASE_PATH) + "bootstrap.tar.gz";
|
||||
std::string tarUrl = std::string(BASE_PATH) + "triangles-bootstrap.tar.gz";
|
||||
|
||||
bool tarDownloaded = DownloadFile(host, tarUrl, tmpTarGz, progressFn, strError);
|
||||
|
||||
|
||||
@@ -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
@@ -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 5
|
||||
#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.
|
||||
|
||||
+4
-6
@@ -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());
|
||||
@@ -1223,7 +1218,10 @@ bool AppInit2()
|
||||
fUseUPnP = false;
|
||||
#endif
|
||||
} else {
|
||||
return InitError(_("Tor failed to start. Triangles requires Tor to operate."));
|
||||
std::string torError = CTorEmbedded::GetInstance()->GetStartupError();
|
||||
if (torError.empty())
|
||||
torError = "No detailed Tor startup error was recorded.";
|
||||
return InitError(strprintf(_("Tor failed to start. Triangles requires Tor to operate.\n\nDetails: %s"), torError.c_str()));
|
||||
}
|
||||
|
||||
// Initialize Tor V3 identity (Ed25519 keys, onion address)
|
||||
|
||||
+6
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+157
-22
@@ -13,6 +13,7 @@
|
||||
#include "kernel.h"
|
||||
#include "smessage.h"
|
||||
#include "tor/onion_v3.h"
|
||||
#include "tor/tor_embedded.h"
|
||||
#ifdef ENABLE_ZMQ
|
||||
#include "zmqpublishnotifier.h"
|
||||
#endif
|
||||
@@ -246,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);
|
||||
@@ -1503,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,
|
||||
@@ -1512,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");
|
||||
}
|
||||
|
||||
|
||||
@@ -2033,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++)
|
||||
{
|
||||
@@ -2340,6 +2370,12 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
vResurrect.push_back(tx);
|
||||
}
|
||||
|
||||
// Remove disconnected PoS blocks from setStakeSeen so they don't
|
||||
// block acceptance of valid blocks on the winning chain.
|
||||
for (CBlockIndex* pindex : vDisconnect)
|
||||
if (pindex->IsProofOfStake())
|
||||
setStakeSeen.erase(make_pair(pindex->prevoutStake, pindex->nStakeTime));
|
||||
|
||||
// Connect longer branch
|
||||
vector<CTransaction> vDelete;
|
||||
for (unsigned int i = 0; i < vConnect.size(); i++)
|
||||
@@ -2504,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())
|
||||
@@ -2764,15 +2800,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 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->GetBlockHash() < pindexBest->GetBlockHash())
|
||||
pindexNew->GetBlockHash() < pindexBest->GetBlockHash() &&
|
||||
GetTime() - nLastEqualTrustReorg > 10 * 60)
|
||||
{
|
||||
fNewBest = true;
|
||||
nLastEqualTrustReorg = GetTime();
|
||||
}
|
||||
|
||||
if (fNewBest)
|
||||
{
|
||||
@@ -2813,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
|
||||
@@ -3117,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
|
||||
@@ -3713,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));
|
||||
@@ -3729,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++)
|
||||
@@ -3743,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++)
|
||||
{
|
||||
@@ -3765,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)
|
||||
{
|
||||
@@ -4063,6 +4130,17 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
|
||||
pfrom->fSuccessfullyConnected = true;
|
||||
|
||||
// If this is an onion peer and we have a pending resolve, request their wallet address
|
||||
{
|
||||
std::string peerAddr = pfrom->addr.ToStringIP();
|
||||
if (peerAddr.find(".onion") != std::string::npos)
|
||||
{
|
||||
CTorV3Manager* torMgr = CTorV3Manager::GetInstance();
|
||||
if (torMgr)
|
||||
torMgr->RequestWalletAddress(pfrom);
|
||||
}
|
||||
}
|
||||
|
||||
printf("receive version message: version %d, blocks=%d, us=%s, them=%s, peer=%s\n", pfrom->nVersion, pfrom->nStartingHeight, addrMe.ToString().c_str(), addrFrom.ToString().c_str(), pfrom->addr.ToString().c_str());
|
||||
|
||||
cPeerBlockCounts.input(pfrom->nStartingHeight);
|
||||
@@ -4729,6 +4807,63 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
}
|
||||
|
||||
|
||||
else if (strCommand == "getwalletaddr")
|
||||
{
|
||||
// Peer is requesting our TRI receiving address for onion resolution.
|
||||
// Respond with address + signature proving ownership.
|
||||
if (!pwalletMain)
|
||||
return true;
|
||||
|
||||
// Get our onion address to sign
|
||||
CTorV3Manager* torMgr = CTorV3Manager::GetInstance();
|
||||
std::string ourOnion = torMgr ? torMgr->GetWalletOnionAddress() : "";
|
||||
if (ourOnion.empty())
|
||||
{
|
||||
CTorEmbedded* torEmbed = CTorEmbedded::GetInstance();
|
||||
if (torEmbed)
|
||||
ourOnion = torEmbed->GetOnionAddress();
|
||||
}
|
||||
if (ourOnion.empty())
|
||||
return true; // Can't prove identity without onion address
|
||||
|
||||
// Get the default receiving address
|
||||
CPubKey pubKey;
|
||||
if (!pwalletMain->GetKeyFromPool(pubKey, true))
|
||||
return true;
|
||||
|
||||
CTrianglesAddress addr(pubKey.GetID());
|
||||
std::string strAddr = addr.ToString();
|
||||
|
||||
// Sign our onion address with the wallet key
|
||||
CKeyID keyID;
|
||||
addr.GetKeyID(keyID);
|
||||
CKey key;
|
||||
if (!pwalletMain->GetKey(keyID, key))
|
||||
return true;
|
||||
|
||||
CDataStream ss(SER_GETHASH, 0);
|
||||
ss << strMessageMagic;
|
||||
ss << ourOnion;
|
||||
|
||||
std::vector<unsigned char> vchSig;
|
||||
if (!key.SignCompact(Hash(ss.begin(), ss.end()), vchSig))
|
||||
return true;
|
||||
|
||||
pfrom->PushMessage("walletaddr", strAddr, vchSig);
|
||||
}
|
||||
|
||||
else if (strCommand == "walletaddr")
|
||||
{
|
||||
// Peer is responding with their TRI address + signature
|
||||
std::string triAddr;
|
||||
std::vector<unsigned char> vchSig;
|
||||
vRecv >> triAddr >> vchSig;
|
||||
|
||||
CTorV3Manager* torMgr = CTorV3Manager::GetInstance();
|
||||
if (torMgr)
|
||||
torMgr->HandleWalletAddrResponse(pfrom, triAddr, vchSig);
|
||||
}
|
||||
|
||||
else if (strCommand == "seeder")
|
||||
{
|
||||
// Receive seeder node announcement
|
||||
|
||||
+6
-2
@@ -62,8 +62,12 @@ static const uint256 hashGenesisBlockTestNet ("0x7e7a6e4dd5fe895106fca912dfbacae
|
||||
inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 3 * 60 : 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 (3-min 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;
|
||||
|
||||
+28
-7
@@ -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)
|
||||
|
||||
@@ -1383,7 +1383,7 @@ QLabel {
|
||||
color: #f26522;
|
||||
}</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,1,0,1,0,0,0,0,0,0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,0,0,1,0,1,0,0,0,0,0,0,0">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
@@ -1406,7 +1406,46 @@ QLabel {
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<width>10</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_onion">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Click to copy .onion address</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::NoTextInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_onion">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
@@ -1514,6 +1553,23 @@ QProgressBar::chunk {
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_v3">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Tor V3 hidden service status</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">V3</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_connections">
|
||||
<property name="text">
|
||||
|
||||
@@ -1506,6 +1506,27 @@ QCheckBox::indicator:checked {
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showOnionAddress">
|
||||
<property name="toolTip">
|
||||
<string>Whether to show this wallet's .onion address in the status bar.</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QCheckBox::indicator {
|
||||
border:1px solid #f26522;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked {
|
||||
image:url(:/icons/checkbox);
|
||||
padding: 1px;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show .onion address in status &bar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_Display">
|
||||
<property name="orientation">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -206,6 +206,7 @@ void OptionsDialog::setMapper()
|
||||
mapper->addMapping(ui->unit, OptionsModel::DisplayUnit);
|
||||
mapper->addMapping(ui->displayAddresses, OptionsModel::DisplayAddresses);
|
||||
mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures);
|
||||
mapper->addMapping(ui->showOnionAddress, OptionsModel::ShowOnionAddress);
|
||||
}
|
||||
|
||||
void OptionsDialog::enableApplyButton()
|
||||
|
||||
@@ -51,6 +51,7 @@ void OptionsModel::Init()
|
||||
fMinimizeToTray = settings.value("fMinimizeToTray", false).toBool();
|
||||
fMinimizeOnClose = settings.value("fMinimizeOnClose", false).toBool();
|
||||
fCoinControlFeatures = settings.value("fCoinControlFeatures", false).toBool();
|
||||
fShowOnionAddress = settings.value("fShowOnionAddress", true).toBool();
|
||||
nTransactionFee = settings.value("nTransactionFee").toLongLong();
|
||||
nReserveBalance = settings.value("nReserveBalance").toLongLong();
|
||||
language = settings.value("language", "").toString();
|
||||
@@ -126,6 +127,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const
|
||||
return settings.value("language", "");
|
||||
case CoinControlFeatures:
|
||||
return QVariant(fCoinControlFeatures);
|
||||
case ShowOnionAddress:
|
||||
return QVariant(fShowOnionAddress);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@@ -226,6 +229,12 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
|
||||
emit coinControlFeaturesChanged(fCoinControlFeatures);
|
||||
}
|
||||
break;
|
||||
case ShowOnionAddress: {
|
||||
fShowOnionAddress = value.toBool();
|
||||
settings.setValue("fShowOnionAddress", fShowOnionAddress);
|
||||
emit showOnionAddressChanged(fShowOnionAddress);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -269,3 +278,8 @@ bool OptionsModel::getDisplayAddresses()
|
||||
{
|
||||
return bDisplayAddresses;
|
||||
}
|
||||
|
||||
bool OptionsModel::getShowOnionAddress()
|
||||
{
|
||||
return fShowOnionAddress;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
DetachDatabases, // bool
|
||||
Language, // QString
|
||||
CoinControlFeatures, // bool
|
||||
ShowOnionAddress, // bool
|
||||
OptionIDRowCount,
|
||||
};
|
||||
|
||||
@@ -52,6 +53,7 @@ public:
|
||||
int getDisplayUnit();
|
||||
bool getDisplayAddresses();
|
||||
bool getCoinControlFeatures();
|
||||
bool getShowOnionAddress();
|
||||
QString getLanguage() { return language; }
|
||||
|
||||
private:
|
||||
@@ -60,6 +62,7 @@ private:
|
||||
bool fMinimizeToTray;
|
||||
bool fMinimizeOnClose;
|
||||
bool fCoinControlFeatures;
|
||||
bool fShowOnionAddress;
|
||||
QString language;
|
||||
|
||||
signals:
|
||||
@@ -67,6 +70,7 @@ signals:
|
||||
void transactionFeeChanged(qint64);
|
||||
void reserveBalanceChanged(qint64);
|
||||
void coinControlFeaturesChanged(bool);
|
||||
void showOnionAddressChanged(bool);
|
||||
};
|
||||
|
||||
#endif // OPTIONSMODEL_H
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <QAbstractItemDelegate>
|
||||
#include <QPainter>
|
||||
#include <QTimer>
|
||||
|
||||
#define DECORATION_SIZE 64
|
||||
#define NUM_ITEMS 5
|
||||
@@ -88,6 +89,7 @@ public:
|
||||
OverviewPage::OverviewPage(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::OverviewPage),
|
||||
clientModel(0),
|
||||
currentBalance(-1),
|
||||
currentStake(0),
|
||||
currentUnconfirmedBalance(-1),
|
||||
@@ -112,6 +114,7 @@ OverviewPage::OverviewPage(QWidget *parent) :
|
||||
|
||||
// start with displaying the "out of sync" warnings
|
||||
showOutOfSyncWarning(true);
|
||||
|
||||
}
|
||||
|
||||
void OverviewPage::handleTransactionClicked(const QModelIndex &index)
|
||||
@@ -125,6 +128,11 @@ OverviewPage::~OverviewPage()
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void OverviewPage::setClientModel(ClientModel *clientModel)
|
||||
{
|
||||
this->clientModel = clientModel;
|
||||
}
|
||||
|
||||
void OverviewPage::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance)
|
||||
{
|
||||
if (!model || !model->getOptionsModel())
|
||||
@@ -213,3 +221,4 @@ void OverviewPage::showOutOfSyncWarning(bool fShow)
|
||||
ui->labelWalletStatus->setVisible(fShow);
|
||||
ui->labelTransactionsStatus->setVisible(fShow);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
|
||||
#include "coincontrol.h"
|
||||
#include "coincontroldialog.h"
|
||||
#include "tor/onion_v3.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QLocale>
|
||||
#include <QApplication>
|
||||
#include <QTextDocument>
|
||||
#include <QScrollBar>
|
||||
#include <QClipboard>
|
||||
@@ -145,6 +147,37 @@ void SendCoinsDialog::on_sendButton_clicked()
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve any .onion addresses to TRI addresses
|
||||
CTorV3Manager* torMgr = CTorV3Manager::GetInstance();
|
||||
for(int i = 0; i < recipients.size(); ++i)
|
||||
{
|
||||
std::string addr = recipients[i].address.toStdString();
|
||||
if (addr.size() == 62 && addr.substr(addr.size() - 6) == ".onion")
|
||||
{
|
||||
std::string triAddr;
|
||||
if (torMgr && torMgr->LookupCachedOnionAddress(addr, triAddr))
|
||||
{
|
||||
// Found in cache — substitute
|
||||
recipients[i].address = QString::fromStdString(triAddr);
|
||||
if (recipients[i].label.isEmpty())
|
||||
recipients[i].label = QString::fromStdString(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not cached — initiate async resolution and tell user to wait
|
||||
if (torMgr)
|
||||
torMgr->ResolveOnionAddress(addr, triAddr);
|
||||
|
||||
QMessageBox::information(this, tr("Resolving .onion Address"),
|
||||
tr("Connecting to %1 to resolve their TRI address.\n\n"
|
||||
"Please wait a moment and try again.")
|
||||
.arg(recipients[i].address));
|
||||
fNewRecipientAllowed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format confirmation message
|
||||
QStringList formatted;
|
||||
foreach(const SendCoinsRecipient &rcp, recipients)
|
||||
|
||||
@@ -23,7 +23,7 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) :
|
||||
#if QT_VERSION >= 0x040700
|
||||
/* Do not move this to the XML file, Qt before 4.7 will choke on it */
|
||||
ui->addAsLabel->setPlaceholderText(tr("Enter a label for this address to add it to your address book"));
|
||||
ui->payTo->setPlaceholderText(tr("Enter a TRI address (e.g. TXc7mPCNFFpinDonuSH5PNVY9S8nBcvGQm)"));
|
||||
ui->payTo->setPlaceholderText(tr("Enter a TRI address or .onion address"));
|
||||
ui->narration->setPlaceholderText(tr("Enter a short note to send with payment. This feature will be enabled later."));
|
||||
ui->narration->setEnabled(false);
|
||||
ui->narration->setVisible(false);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "sendmessagesentry.h"
|
||||
#include "dialog_move_handler.h"
|
||||
#include "guiutil.h"
|
||||
#include "tor/onion_v3.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QLocale>
|
||||
@@ -155,6 +156,35 @@ void SendMessagesDialog::on_sendButton_clicked()
|
||||
if(!valid || recipients.isEmpty())
|
||||
return;
|
||||
|
||||
// Resolve any .onion addresses to TRI addresses
|
||||
CTorV3Manager* torMgr = CTorV3Manager::GetInstance();
|
||||
for(int i = 0; i < recipients.size(); ++i)
|
||||
{
|
||||
std::string addr = recipients[i].address.toStdString();
|
||||
if (addr.size() == 62 && addr.substr(addr.size() - 6) == ".onion")
|
||||
{
|
||||
std::string triAddr;
|
||||
if (torMgr && torMgr->LookupCachedOnionAddress(addr, triAddr))
|
||||
{
|
||||
recipients[i].address = QString::fromStdString(triAddr);
|
||||
if (recipients[i].label.isEmpty())
|
||||
recipients[i].label = QString::fromStdString(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (torMgr)
|
||||
torMgr->ResolveOnionAddress(addr, triAddr);
|
||||
|
||||
QMessageBox::information(this, tr("Resolving .onion Address"),
|
||||
tr("Connecting to %1 to resolve their TRI address.\n\n"
|
||||
"Please wait a moment and try again.")
|
||||
.arg(recipients[i].address));
|
||||
fNewRecipientAllowed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format confirmation message
|
||||
QStringList formatted;
|
||||
foreach(const SendMessagesRecipient &rcp, recipients)
|
||||
|
||||
@@ -25,7 +25,7 @@ SendMessagesEntry::SendMessagesEntry(QWidget *parent) :
|
||||
#if QT_VERSION >= 0x040700
|
||||
/* Do not move this to the XML file, Qt before 4.7 will choke on it */
|
||||
ui->addAsLabel->setPlaceholderText(tr("Enter a label for this address to add it to your address book"));
|
||||
ui->sendTo->setPlaceholderText(tr("Enter a TRI address (e.g. TXc7mPCNFFpinDonuSH5PNVY9S8nBcvGQm)"));
|
||||
ui->sendTo->setPlaceholderText(tr("Enter a TRI address or .onion address"));
|
||||
ui->publicKey->setPlaceholderText(tr("Enter the public key for the address above, it is not in the blockchain"));
|
||||
ui->messageText->setErrorText(tr("You cannot send a blank message!"));
|
||||
#endif
|
||||
|
||||
@@ -50,11 +50,27 @@ QValidator::State TrianglesAddressValidator::validate(QString &input, int &pos)
|
||||
|
||||
// Validation
|
||||
QValidator::State state = QValidator::Acceptable;
|
||||
|
||||
// Allow .onion addresses (base32 lowercase + digits 2-7 + period)
|
||||
bool isOnion = input.endsWith(".onion");
|
||||
|
||||
for(int idx=0; idx<input.size(); ++idx)
|
||||
{
|
||||
int ch = input.at(idx).unicode();
|
||||
|
||||
if(((ch >= '0' && ch<='9') ||
|
||||
if (isOnion)
|
||||
{
|
||||
// V3 onion: base32 (a-z, 2-7) + ".onion" suffix
|
||||
if ((ch >= 'a' && ch <= 'z') || (ch >= '2' && ch <= '7') || ch == '.')
|
||||
{
|
||||
// Valid onion character
|
||||
}
|
||||
else
|
||||
{
|
||||
state = QValidator::Invalid;
|
||||
}
|
||||
}
|
||||
else if(((ch >= '0' && ch<='9') ||
|
||||
(ch >= 'a' && ch<='z') ||
|
||||
(ch >= 'A' && ch<='Z')) &&
|
||||
ch != 'l' && ch != 'I' && ch != '0' && ch != 'O')
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
#include "util.h"
|
||||
//#include "message_box_dialog.h"
|
||||
#include "wallet.h"
|
||||
#include "tor/tor_embedded.h"
|
||||
#include "tor/onion_v3.h"
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include "macdockiconhandler.h"
|
||||
@@ -66,6 +68,8 @@
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
#include <QClipboard>
|
||||
#include <QToolTip>
|
||||
#include <QDragEnterEvent>
|
||||
#if QT_VERSION < 0x050000
|
||||
#include <QUrl>
|
||||
@@ -336,6 +340,21 @@ TrianglesGUI::TrianglesGUI(bool fIsTestnet, QWidget *parent):
|
||||
updateStakingIcon();
|
||||
}
|
||||
|
||||
// Onion address in status bar (hidden until populated, click to copy)
|
||||
labelOnionAddress = ui->label_onion;
|
||||
labelOnionAddress->setVisible(false);
|
||||
labelOnionAddress->setCursor(Qt::PointingHandCursor);
|
||||
labelOnionAddress->installEventFilter(this);
|
||||
|
||||
// V3 indicator next to staking icon (hidden until onion is active)
|
||||
labelV3Icon = ui->label_v3;
|
||||
labelV3Icon->setVisible(false);
|
||||
|
||||
QTimer *timerOnion = new QTimer(this);
|
||||
connect(timerOnion, SIGNAL(timeout()), this, SLOT(updateOnionAddress()));
|
||||
timerOnion->start(5000);
|
||||
updateOnionAddress();
|
||||
|
||||
QTimer *timerShutdown = new QTimer(this);
|
||||
connect(timerShutdown, SIGNAL(timeout()), this, SLOT(detectShutdown()));
|
||||
timerShutdown->start(200);
|
||||
@@ -569,6 +588,9 @@ void TrianglesGUI::setClientModel(ClientModel *clientModel)
|
||||
rpcConsole->setClientModel(clientModel);
|
||||
addressBookPage->setOptionsModel(clientModel->getOptionsModel());
|
||||
receiveCoinsPage->setOptionsModel(clientModel->getOptionsModel());
|
||||
|
||||
connect(clientModel->getOptionsModel(), SIGNAL(showOnionAddressChanged(bool)), this, SLOT(updateOnionAddress()));
|
||||
updateOnionAddress();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1289,6 +1311,16 @@ bool TrianglesGUI::eventFilter(QObject *object, QEvent *event)
|
||||
}
|
||||
if (object == ui->pushButton_Overview && event->type() == QEvent::MouseButtonPress)
|
||||
gotoOverviewPage();
|
||||
if (object == labelOnionAddress && event->type() == QEvent::MouseButtonPress)
|
||||
{
|
||||
QString addr = labelOnionAddress->text();
|
||||
if (!addr.isEmpty())
|
||||
{
|
||||
QApplication::clipboard()->setText(addr);
|
||||
QToolTip::showText(QCursor::pos(), tr("Copied!"), labelOnionAddress);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return QMainWindow::eventFilter(object, event);
|
||||
}
|
||||
|
||||
@@ -1715,6 +1747,42 @@ void TrianglesGUI::detectShutdown()
|
||||
QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void TrianglesGUI::updateOnionAddress()
|
||||
{
|
||||
std::string onionAddress = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
|
||||
if (onionAddress.empty())
|
||||
onionAddress = CTorEmbedded::GetInstance()->GetOnionAddress();
|
||||
|
||||
bool hasOnion = !onionAddress.empty();
|
||||
|
||||
// V3 indicator — always visible when onion is active, independent of the address toggle
|
||||
if (hasOnion) {
|
||||
labelV3Icon->setStyleSheet("color: #00ff00; font-weight: bold;");
|
||||
labelV3Icon->setToolTip(tr("V3 Tor enabled"));
|
||||
labelV3Icon->setVisible(true);
|
||||
} else {
|
||||
labelV3Icon->setStyleSheet("color: #555555; font-weight: bold;");
|
||||
labelV3Icon->setToolTip(tr("V3 Tor not active"));
|
||||
labelV3Icon->setVisible(true);
|
||||
}
|
||||
|
||||
// Onion address text — respects user preference
|
||||
if (clientModel && clientModel->getOptionsModel() &&
|
||||
!clientModel->getOptionsModel()->getShowOnionAddress()) {
|
||||
labelOnionAddress->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasOnion) {
|
||||
labelOnionAddress->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
labelOnionAddress->setText(QString::fromStdString(onionAddress));
|
||||
labelOnionAddress->setToolTip(tr("This wallet's Tor .onion address. Selectable — right-click to copy."));
|
||||
labelOnionAddress->setVisible(true);
|
||||
}
|
||||
|
||||
|
||||
void TrianglesGUI::on_bHelp_clicked()
|
||||
{
|
||||
|
||||
@@ -109,6 +109,8 @@ private:
|
||||
QLabel *labelStakingIcon;
|
||||
QLabel *labelConnectionsIcon;
|
||||
QLabel *labelBlocksIcon;
|
||||
QLabel *labelOnionAddress;
|
||||
QLabel *labelV3Icon;
|
||||
QLabel *progressBarLabel;
|
||||
QProgressBar *progressBar;
|
||||
|
||||
@@ -174,6 +176,7 @@ public slots:
|
||||
void setEncryptionStatus(int status);
|
||||
void setWalletTransactionSyncState(bool syncing);
|
||||
void setWalletTransactionSyncProgress(bool syncing, int pendingNotifications);
|
||||
void updateOnionAddress();
|
||||
|
||||
/** Notify the user of an error in the network or transaction handling code. */
|
||||
void error(const QString &title, const QString &message, bool modal);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "walletdb.h" // for BackupWallet
|
||||
#include "base58.h"
|
||||
#include "main.h"
|
||||
#include "tor/onion_v3.h"
|
||||
|
||||
#include <QSet>
|
||||
#include <QTimer>
|
||||
@@ -281,8 +282,11 @@ void WalletModel::updateAddressBook(const QString &address, const QString &label
|
||||
bool WalletModel::validateAddress(const QString &address)
|
||||
{
|
||||
std::string sAddr = address.toStdString();
|
||||
|
||||
|
||||
|
||||
// Accept V3 .onion addresses (56-char base32 + ".onion" = 62 chars)
|
||||
if (sAddr.size() == 62 && sAddr.substr(sAddr.size() - 6) == ".onion")
|
||||
return CTorV3Service::ValidateOnionAddress(sAddr);
|
||||
|
||||
CTrianglesAddress addressParsed(sAddr);
|
||||
return addressParsed.IsValid();
|
||||
}
|
||||
@@ -385,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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,6 +6,10 @@
|
||||
#include "../main.h"
|
||||
#include "../kernel.h"
|
||||
|
||||
// These globals are defined in main.cpp but not declared in any header
|
||||
extern unsigned int nTargetSpacing;
|
||||
extern unsigned int nStakeMaxAge;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(consensus_tests)
|
||||
|
||||
// --- Chain constants that must never change ---
|
||||
@@ -75,20 +79,16 @@ BOOST_AUTO_TEST_CASE(pow_cutoff)
|
||||
BOOST_AUTO_TEST_CASE(timing_constants)
|
||||
{
|
||||
// 2-minute block target
|
||||
extern unsigned int nTargetSpacing;
|
||||
BOOST_CHECK_EQUAL(nTargetSpacing, 120u);
|
||||
BOOST_CHECK_EQUAL(::nTargetSpacing, 120u);
|
||||
|
||||
// 1-hour minimum stake age
|
||||
extern unsigned int nStakeMinAge;
|
||||
BOOST_CHECK_EQUAL(nStakeMinAge, 3600u);
|
||||
BOOST_CHECK_EQUAL(::nStakeMinAge, 3600u);
|
||||
|
||||
// 12-hour maximum stake age (pre-V5 fork)
|
||||
extern unsigned int nStakeMaxAge;
|
||||
BOOST_CHECK_EQUAL(nStakeMaxAge, 43200u);
|
||||
BOOST_CHECK_EQUAL(::nStakeMaxAge, 43200u);
|
||||
|
||||
// Modifier interval
|
||||
extern unsigned int nModifierInterval;
|
||||
BOOST_CHECK_EQUAL(nModifierInterval, 300u); // 5 minutes
|
||||
BOOST_CHECK_EQUAL(::nModifierInterval, 300u); // 5 minutes
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(coinbase_maturity)
|
||||
|
||||
@@ -146,7 +146,7 @@ BOOST_AUTO_TEST_CASE(block_header_roundtrip)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(block_current_version)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(CBlock::CURRENT_VERSION, 6);
|
||||
BOOST_CHECK_EQUAL((int)CBlock::CURRENT_VERSION, 6);
|
||||
}
|
||||
|
||||
// --- CDiskTxPos ---
|
||||
|
||||
@@ -2286,6 +2286,160 @@ void ThreadTorMaintenance(void* parg)
|
||||
printf("Tor maintenance thread exited\n");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Onion-to-TRI address resolution
|
||||
// ============================================================================
|
||||
|
||||
void CTorV3Manager::CacheOnionAddress(const std::string& onion, const std::string& triAddr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cs_onionAddr);
|
||||
mapOnionToAddress[onion] = {triAddr, GetTime()};
|
||||
printf("Cached onion→TRI mapping: %s → %s\n", onion.c_str(), triAddr.c_str());
|
||||
}
|
||||
|
||||
bool CTorV3Manager::LookupCachedOnionAddress(const std::string& onion, std::string& triAddr) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cs_onionAddr);
|
||||
auto it = mapOnionToAddress.find(onion);
|
||||
if (it != mapOnionToAddress.end())
|
||||
{
|
||||
// Cache entries expire after 24 hours
|
||||
if (GetTime() - it->second.timestamp < 86400)
|
||||
{
|
||||
triAddr = it->second.triAddress;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CTorV3Manager::RequestWalletAddress(CNode* pnode)
|
||||
{
|
||||
if (pnode)
|
||||
pnode->PushMessage("getwalletaddr");
|
||||
}
|
||||
|
||||
void CTorV3Manager::HandleWalletAddrResponse(CNode* pfrom, const std::string& triAddr,
|
||||
const std::vector<unsigned char>& vchSig)
|
||||
{
|
||||
// Extract the peer's onion address from the connection
|
||||
std::string peerOnion = pfrom->addr.ToStringIP();
|
||||
if (peerOnion.find(".onion") == std::string::npos)
|
||||
{
|
||||
printf("walletaddr: peer %s is not an onion address, ignoring\n", peerOnion.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the signature: peer signed their onion address with the TRI key
|
||||
extern const std::string strMessageMagic;
|
||||
CDataStream ss(SER_GETHASH, 0);
|
||||
ss << strMessageMagic;
|
||||
ss << peerOnion;
|
||||
|
||||
CKey key;
|
||||
if (!key.SetCompactSignature(Hash(ss.begin(), ss.end()), vchSig))
|
||||
{
|
||||
printf("walletaddr: invalid signature from %s\n", peerOnion.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the recovered public key matches the claimed TRI address
|
||||
CTrianglesAddress claimedAddr(triAddr);
|
||||
if (!claimedAddr.IsValid())
|
||||
{
|
||||
printf("walletaddr: invalid TRI address '%s' from %s\n", triAddr.c_str(), peerOnion.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
CKeyID claimedKeyID;
|
||||
if (!claimedAddr.GetKeyID(claimedKeyID))
|
||||
{
|
||||
printf("walletaddr: cannot extract KeyID from %s\n", triAddr.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.GetPubKey().GetID() != claimedKeyID)
|
||||
{
|
||||
printf("walletaddr: signature does not match claimed address %s from %s\n",
|
||||
triAddr.c_str(), peerOnion.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Signature valid — cache the mapping
|
||||
CacheOnionAddress(peerOnion, triAddr);
|
||||
|
||||
// Fire any pending resolve callbacks
|
||||
std::function<void(bool, const std::string&)> callback;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cs_onionAddr);
|
||||
auto it = mapPendingResolves.find(peerOnion);
|
||||
if (it != mapPendingResolves.end())
|
||||
{
|
||||
callback = it->second;
|
||||
mapPendingResolves.erase(it);
|
||||
}
|
||||
}
|
||||
if (callback)
|
||||
callback(true, triAddr);
|
||||
}
|
||||
|
||||
bool CTorV3Manager::ResolveOnionAddress(const std::string& onion, std::string& triAddr,
|
||||
std::function<void(bool, const std::string&)> callback)
|
||||
{
|
||||
// Check cache first
|
||||
if (LookupCachedOnionAddress(onion, triAddr))
|
||||
return true;
|
||||
|
||||
// Find or connect to the peer
|
||||
std::string onionHost = onion;
|
||||
// Strip trailing .onion:port if present
|
||||
size_t colonPos = onionHost.find(':');
|
||||
if (colonPos != std::string::npos)
|
||||
onionHost = onionHost.substr(0, colonPos);
|
||||
|
||||
// Search connected peers for this onion address
|
||||
CNode* pnode = nullptr;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pn : vNodes)
|
||||
{
|
||||
if (pn->addr.ToStringIP().find(onionHost) != std::string::npos)
|
||||
{
|
||||
pnode = pn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pnode)
|
||||
{
|
||||
// Already connected — request their address
|
||||
if (callback)
|
||||
RegisterResolveCallback(onionHost, callback);
|
||||
RequestWalletAddress(pnode);
|
||||
return false; // async — callback will fire when response arrives
|
||||
}
|
||||
|
||||
// Not connected — try to connect
|
||||
if (callback)
|
||||
RegisterResolveCallback(onionHost, callback);
|
||||
|
||||
int port = GetDefaultPort();
|
||||
ConnectToOnionPeer(onionHost, port);
|
||||
|
||||
// After connection, the version handshake will complete, then we
|
||||
// send the request. We need a short delay to let the connection establish.
|
||||
// The caller should use the callback for async notification.
|
||||
return false;
|
||||
}
|
||||
|
||||
void CTorV3Manager::RegisterResolveCallback(const std::string& onion,
|
||||
std::function<void(bool, const std::string&)> callback)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cs_onionAddr);
|
||||
mapPendingResolves[onion] = callback;
|
||||
}
|
||||
|
||||
// Global functions
|
||||
bool InitTorV3()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
|
||||
// Forward declarations
|
||||
class CNode;
|
||||
@@ -137,6 +139,25 @@ public:
|
||||
void UpdateSeederReputation(const std::string& seederAddress, bool success);
|
||||
void RequestSeederListFromPeer(const std::string& peerAddress);
|
||||
void ScheduleSeederReannouncement();
|
||||
|
||||
// Onion-to-TRI address resolution
|
||||
struct OnionAddrEntry {
|
||||
std::string triAddress;
|
||||
int64_t timestamp;
|
||||
};
|
||||
std::map<std::string, OnionAddrEntry> mapOnionToAddress;
|
||||
mutable std::mutex cs_onionAddr;
|
||||
std::map<std::string, std::function<void(bool, const std::string&)>> mapPendingResolves;
|
||||
|
||||
void CacheOnionAddress(const std::string& onion, const std::string& triAddr);
|
||||
bool LookupCachedOnionAddress(const std::string& onion, std::string& triAddr) const;
|
||||
void RequestWalletAddress(CNode* pnode);
|
||||
void HandleWalletAddrResponse(CNode* pfrom, const std::string& triAddr,
|
||||
const std::vector<unsigned char>& vchSig);
|
||||
bool ResolveOnionAddress(const std::string& onion, std::string& triAddr,
|
||||
std::function<void(bool, const std::string&)> callback = nullptr);
|
||||
void RegisterResolveCallback(const std::string& onion,
|
||||
std::function<void(bool, const std::string&)> callback);
|
||||
};
|
||||
|
||||
// Tor V3 configuration
|
||||
|
||||
@@ -114,6 +114,7 @@ bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
|
||||
{
|
||||
if (running.load()) return true;
|
||||
|
||||
lastError.clear();
|
||||
socksPort = socks;
|
||||
hiddenServiceEnabled = enableHiddenService;
|
||||
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
|
||||
@@ -202,11 +203,13 @@ bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
|
||||
}
|
||||
|
||||
if (!running.load()) {
|
||||
lastError = "Embedded Tor thread exited during bootstrap before the SOCKS proxy became available.";
|
||||
printf("ERROR: Embedded Tor thread exited during bootstrap\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
lastError = strprintf("Embedded Tor did not expose SOCKS port %d within 60 seconds.", socksPort);
|
||||
printf("WARNING: Embedded Tor started but SOCKS not ready after 60s (still bootstrapping)\n");
|
||||
return true;
|
||||
}
|
||||
@@ -241,8 +244,12 @@ bool CTorEmbedded::Start(int socks, int hsPort, bool enableHiddenService)
|
||||
hiddenServiceEnabled = enableHiddenService;
|
||||
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
|
||||
onionHostname.clear();
|
||||
lastError.clear();
|
||||
torDataDir = (::GetDataDir() / "tor_data").string();
|
||||
running.store(StartTorProcess(torDataDir, socksPort, hiddenServicePort, hiddenServiceEnabled));
|
||||
if (!running.load()) {
|
||||
lastError = CTorProcess::GetInstance()->GetStartupError();
|
||||
}
|
||||
return running.load();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ private:
|
||||
bool hiddenServiceEnabled;
|
||||
std::string torDataDir;
|
||||
std::string onionHostname;
|
||||
std::string lastError;
|
||||
|
||||
public:
|
||||
static CTorEmbedded* GetInstance();
|
||||
@@ -43,6 +44,8 @@ public:
|
||||
|
||||
// Get our .onion address (available after bootstrap)
|
||||
std::string GetOnionAddress() const { return onionHostname; }
|
||||
std::string GetStartupError() const { return lastError; }
|
||||
void SetStartupError(const std::string& value) { lastError = value; }
|
||||
|
||||
// Get the hidden service port
|
||||
int GetHiddenServicePort() const { return hiddenServicePort; }
|
||||
|
||||
+49
-3
@@ -19,11 +19,13 @@
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <fstream>
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
@@ -35,6 +37,28 @@
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
static std::string ReadTailLines(const fs::path& filePath, size_t maxLines)
|
||||
{
|
||||
std::ifstream in(filePath.string().c_str());
|
||||
if (!in.is_open()) return "";
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
lines.push_back(line);
|
||||
if (lines.size() > maxLines) {
|
||||
lines.erase(lines.begin());
|
||||
}
|
||||
}
|
||||
|
||||
std::ostringstream out;
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
if (i) out << " | ";
|
||||
out << lines[i];
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
static CTorProcess* torProcessInstance = nullptr;
|
||||
|
||||
CTorProcess* CTorProcess::GetInstance()
|
||||
@@ -199,6 +223,10 @@ bool CTorProcess::WriteTorrc()
|
||||
fs::create_directories(torStateDir);
|
||||
torrc << "DataDirectory " << torStateDir.string() << "\n";
|
||||
|
||||
// Persistent Tor log for post-mortem debugging on user machines.
|
||||
fs::path torLogPath = dataPath / "tor.log";
|
||||
torrc << "Log notice file " << torLogPath.string() << "\n";
|
||||
|
||||
if (hiddenServiceEnabled) {
|
||||
// V3 hidden service so this node is reachable via .onion
|
||||
torrc << "HiddenServiceDir " << hsDir.string() << "\n";
|
||||
@@ -207,8 +235,12 @@ bool CTorProcess::WriteTorrc()
|
||||
<< " 127.0.0.1:" << hiddenServicePort << "\n";
|
||||
}
|
||||
|
||||
// Reduce bandwidth/resource usage for wallet use
|
||||
torrc << "ClientOnly 1\n";
|
||||
// Only force client-only mode when we are NOT publishing a hidden service.
|
||||
// When HiddenServicePort is configured, Tor needs to behave as a hidden
|
||||
// service host, not just as a pure outbound client proxy.
|
||||
if (!hiddenServiceEnabled) {
|
||||
torrc << "ClientOnly 1\n";
|
||||
}
|
||||
|
||||
// Disable unused features
|
||||
torrc << "AvoidDiskWrites 1\n";
|
||||
@@ -228,6 +260,7 @@ bool CTorProcess::WriteTorrc()
|
||||
|
||||
bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool enableHiddenService)
|
||||
{
|
||||
lastError.clear();
|
||||
socksPort = socks;
|
||||
hiddenServiceEnabled = enableHiddenService;
|
||||
hiddenServicePort = hiddenServiceEnabled ? hsPort : 0;
|
||||
@@ -236,6 +269,7 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
|
||||
// Check if something is already listening on our SOCKS port
|
||||
if (IsPortInUse(socksPort)) {
|
||||
printf("Tor SOCKS port %d already in use - assuming Tor is running\n", socksPort);
|
||||
lastError = strprintf("SOCKS port %d is already in use; assuming an existing Tor instance is serving it.", socksPort);
|
||||
running = true;
|
||||
return true;
|
||||
}
|
||||
@@ -243,6 +277,7 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
|
||||
// Find Tor binary
|
||||
torBinaryPath = FindTorBinary();
|
||||
if (torBinaryPath.empty()) {
|
||||
lastError = "Tor binary was not found in the bundled install or standard search paths.";
|
||||
printf("WARNING: Tor binary not found. Install Tor for .onion connectivity.\n");
|
||||
printf(" Windows: Download from https://www.torproject.org/download/tor/\n");
|
||||
printf(" Linux: apt install tor or yum install tor\n");
|
||||
@@ -252,6 +287,7 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
|
||||
|
||||
// Write configuration
|
||||
if (!WriteTorrc()) {
|
||||
lastError = strprintf("Failed to write Tor configuration to %s", torrcPath.c_str());
|
||||
printf("ERROR: Failed to write Tor configuration\n");
|
||||
return false;
|
||||
}
|
||||
@@ -278,7 +314,9 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
|
||||
NULL, NULL,
|
||||
&si, &pi))
|
||||
{
|
||||
printf("ERROR: Failed to start Tor process (error %lu)\n", GetLastError());
|
||||
DWORD err = ::GetLastError();
|
||||
lastError = strprintf("CreateProcess failed for Tor binary '%s' with Windows error %lu", torBinaryPath.c_str(), err);
|
||||
printf("ERROR: Failed to start Tor process (error %lu)\n", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -338,12 +376,20 @@ bool CTorProcess::Start(const std::string& dataDir, int socks, int hsPort, bool
|
||||
|
||||
// Check if Tor process is still alive
|
||||
if (!IsRunning()) {
|
||||
fs::path torLogPath = fs::path(torDataDir) / "tor.log";
|
||||
std::string torLogTail = ReadTailLines(torLogPath, 8);
|
||||
if (!torLogTail.empty()) {
|
||||
lastError = strprintf("Tor process exited during bootstrap before the SOCKS port became ready. Recent tor.log: %s", torLogTail.c_str());
|
||||
} else {
|
||||
lastError = "Tor process exited during bootstrap before the SOCKS port became ready.";
|
||||
}
|
||||
printf("ERROR: Tor process exited prematurely\n");
|
||||
running = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
lastError = strprintf("Tor process started from '%s' but SOCKS port %d was not ready after 30 seconds.", torBinaryPath.c_str(), socksPort);
|
||||
printf("WARNING: Tor started but SOCKS proxy not yet ready after 30s\n");
|
||||
printf(" Tor may still be bootstrapping. .onion connections will work once ready.\n");
|
||||
return true;
|
||||
|
||||
@@ -19,6 +19,7 @@ private:
|
||||
std::string torBinaryPath;
|
||||
std::string torDataDir;
|
||||
std::string torrcPath;
|
||||
std::string lastError;
|
||||
int socksPort;
|
||||
int hiddenServicePort;
|
||||
bool hiddenServiceEnabled;
|
||||
@@ -59,6 +60,7 @@ public:
|
||||
|
||||
// Get the Tor binary path (for diagnostics)
|
||||
std::string GetBinaryPath() const { return torBinaryPath; }
|
||||
std::string GetStartupError() const { return lastError; }
|
||||
|
||||
// Singleton access
|
||||
static CTorProcess* GetInstance();
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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 5
|
||||
#define DISPLAY_VERSION_BUILD 0
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user