Compare commits

...

26 Commits

Author SHA1 Message Date
Sami Ahmed 2ac88b4e0a Add finality checkpoint at 2205000 (anti-fork); v5.9.12 2026-06-13 15:09:44 -07:00
sami7777 c1340441cc Bump version to v5.9.11 2026-06-11 02:57:42 -07:00
sami7777 c99d859781 Fix use-after-move crash in orphan block handling; add -ignoredupstake recovery flag
mapOrphanBlocksByPrev.insert dereferenced pblock2 after std::move'ing it into mapOrphanBlocks - guaranteed null deref (segfault at offset 4) on every orphan block received. Capture hashPrevBlock and the raw pointer before the move.

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

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

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

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

Also fix corrupted checkpoint hash at block 3935: truncated '07' in
both mainnet and testnet tables during C++20 modernization.
2026-06-04 18:21:43 -07:00
Krystie e8edcd4aa6 Merge remote-tracking branch 'gitea/master' 2026-05-30 23:01:53 -07:00
Krystie 3928f86657 Merge remote-tracking branch 'gitea/master' into cpp20-modernization 2026-05-30 23:00:56 -07:00
Krystie 67d838433d Fix getblocks: serve main chain to fork peers instead of banning them
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / test-linux-sanitizers (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled
Fork nodes (DNS3/DNS2 stuck on block 570 chain) send locators that
don't match any main chain block. Instead of disconnecting/ banning
after 3 failed getblocks attempts, this change:

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

This fixes the 'no common blocks' deadlock while preserving chain
integrity — only a genuinely longer chain can trigger the reorg.
2026-05-24 22:36:13 -07:00
Krystie e3aeff002c Remove block 570 checkpoint - let nodes sync naturally to main chain
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / test-linux-sanitizers (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled
2026-05-24 21:54:42 -07:00
Krystie 09ccd4339c IBD stall fix: restore IsInitialBlockDownload() time check, add block 570 checkpoint, fix version handler IBD bypass 2026-05-24 15:40:29 -07:00
Krystie 372b252294 Fix Windows CI: use bash shell for git submodule commands
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / test-linux-sanitizers (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled
On Windows MSYS2 runners, the default shell is 'msys2' which doesn't
understand 'git submodule' commands the same way. Adding shell: bash
forces the step to use bash, which properly executes git and finds
submodule content.

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

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

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

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

2. init.cpp: Allow -notor to actually bypass Tor requirement
   - Previously, -notor made StartEmbeddedTor() return false,
     which hit the 'Tor failed to start' error path and killed
     the wallet. Now -notor enables clearnet-only mode for
     diagnostics, benchmarking, and recovery.
   - Updated help text to reflect actual behavior.
2026-04-29 16:39:59 -07:00
Krystie b4308e42ad Smoke-test the Krystie loop runner
Krystie Gate / Static gate (red-list / test-first / no-clearnet) (push) Successful in 35s
Krystie Gate / Build + ctest (push) Successful in 7m11s
Krystie Gate / Auto-merge to master (push) Successful in 28s
This issue was created to exercise the autonomous runner end-to-end.

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

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

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

See: krystie-buildout/workflows/* in the krystie repo for sources.
2026-04-28 21:42:57 -07:00
16 changed files with 1054 additions and 69 deletions
+65
View File
@@ -0,0 +1,65 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
=zWaw
-----END PGP PUBLIC KEY BLOCK-----
+262
View File
@@ -0,0 +1,262 @@
#!/usr/bin/env python3
"""Krystie Gate — static check stage of the CI gate.
Runs inside the Gitea Actions runner. Inspects all commits that were just
pushed to a krystie-wip/* branch and rejects if any violates the gate rules.
Decision per commit:
* If signed by Krystie's GPG key (fingerprint DCF2579968107984), apply the
full per-repo gate.
* If signed by a different key OR unsigned, allow (Sami's authority).
Per-repo enforcement:
* triangles_v5 : red-list (consensus paths) + test-first + no-clearnet
* triangles-explorer, triangles-api, tridock-web-wallet, sami-chat, tri-pi:
test-first only
* homebrew-triangles: formula syntax check only
Outputs:
* On reject, prints REJECTED lines to stderr and exits 1.
* On accept, sets `is_krystie_commit` GH-actions output to true/false.
"""
from __future__ import annotations
import os
import re
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
# Krystie's GPG identity. We accept both the primary long-ID and the
# signing subkey because `git log %GK` returns the subkey that was actually
# used to sign, not the primary. The full primary fingerprint is also
# included so a paranoid future check can validate the chain.
KRYSTIE_PRIMARY_FP = "523A81833EB7201573E1EFE1DCF2579968107984"
KRYSTIE_KEY_IDS = {
"DCF2579968107984", # primary long-ID
"C2DC60618C85A159", # signing subkey long-ID
}
RED_LIST_TRIANGLES_V5 = [
re.compile(r"^src/main\.(cpp|h)$"),
re.compile(r"^src/validation.*"),
re.compile(r"^src/kernel\.(cpp|h)$"),
re.compile(r"^src/checkpoints\.(cpp|h)$"),
re.compile(r"^src/consensus/"),
re.compile(r"^src/protocol\.(cpp|h)$"),
re.compile(r"^src/net\.(cpp|h)$"),
re.compile(r"^src/netbase\.(cpp|h)$"),
re.compile(r"^src/net_bootstrap\.(cpp|h)$"),
re.compile(r"^src/chainparams.*"),
re.compile(r"^src/clientversion\.h$"),
re.compile(r"^src/key\.(cpp|h)$"),
re.compile(r"^src/keystore\.(cpp|h)$"),
re.compile(r"^src/onionseed\.h$"),
re.compile(r"^contrib/seeds/"),
re.compile(r"^contrib/devtools/release.*"),
re.compile(r"^doc/release-process\.txt$"),
]
TEST_DIRS = {
"triangles_v5": ["src/test/", "test/"],
"triangles-explorer": ["src/__tests__/", "tests/", "test/"],
"triangles-api": ["test/", "__tests__/", "tests/"],
"tridock-web-wallet": ["test/", "__tests__/", "tests/"],
"sami-chat": ["test/", "__tests__/", "tests/"],
"tri-pi": ["test/", "tests/"],
"homebrew-triangles": [],
}
SOURCE_EXTS = {
"triangles_v5": {".cpp", ".h", ".c"},
"triangles-explorer": {".ts", ".tsx", ".js", ".svelte"},
"triangles-api": {".js", ".ts"},
"tridock-web-wallet": {".ts", ".tsx", ".js", ".svelte", ".vue"},
"sami-chat": {".ts", ".tsx", ".js", ".svelte", ".vue"},
"tri-pi": {".py", ".sh", ".ts", ".js"},
"homebrew-triangles": set(),
}
RED_LIST_REPOS = {"triangles_v5"}
PEER_CONFIG_PATHS = [
re.compile(r"^contrib/seeds/"),
re.compile(r"^src/chainparams.*"),
re.compile(r".*triangles\.conf(\.example)?$"),
]
@dataclass
class GateResult:
ok: bool
reason: str = ""
def repo_name() -> str:
repo = os.environ.get("GITHUB_REPOSITORY", "")
return repo.split("/", 1)[1] if "/" in repo else repo
def commit_signer(sha: str) -> str | None:
try:
out = subprocess.run(
["git", "log", "-1", "--format=%GK", sha],
check=True, capture_output=True, text=True,
).stdout.strip()
return out or None
except subprocess.CalledProcessError:
return None
def is_krystie_commit(sha: str) -> bool:
fp = commit_signer(sha)
if not fp:
return False
# Accept any key ID we know belongs to Krystie. `git log %GK` returns the
# signing subkey, so we have to whitelist both primary and subkey.
return any(fp == known or known.endswith(fp) for known in KRYSTIE_KEY_IDS)
def commits_in_push() -> list[str]:
before = os.environ.get("GITHUB_BEFORE", "")
sha = os.environ.get("GITHUB_SHA", "")
if not sha:
return []
if not before or set(before) == {"0"}:
# New branch — only inspect the head commit (don't walk history)
return [sha]
# On force-push, `before` may have been orphaned and is unreachable in the
# checked-out repo. `git rev-list before..sha` then exits 128. Fall back
# to inspecting the new head only — that's the safest guarantee we can
# make about what just landed.
try:
out = subprocess.run(
["git", "rev-list", f"{before}..{sha}"],
check=True, capture_output=True, text=True,
).stdout
return [c for c in out.split() if c]
except subprocess.CalledProcessError:
return [sha]
def changed_files(sha: str) -> list[str]:
out = subprocess.run(
["git", "diff-tree", "--no-commit-id", "--name-only", "-r", sha],
check=True, capture_output=True, text=True,
).stdout
return [f for f in out.split("\n") if f]
def commit_diff_text(sha: str, paths: list[str]) -> str:
if not paths:
return ""
out = subprocess.run(
["git", "show", "--no-color", sha, "--"] + paths,
check=True, capture_output=True, text=True,
).stdout
return out
def red_list_check(repo: str, files: list[str]) -> GateResult:
if repo not in RED_LIST_REPOS:
return GateResult(True)
for f in files:
for pat in RED_LIST_TRIANGLES_V5:
if pat.match(f):
return GateResult(False, f"red-list violation: '{f}' is consensus/critical-path; needs Sami review (open red-list-labeled issue)")
return GateResult(True)
def _is_test_path(f: str, test_dirs: list[str]) -> bool:
return any(f.startswith(d) for d in test_dirs) or "/test/" in f or "/tests/" in f or "/__tests__/" in f
def test_first_check(repo: str, files: list[str]) -> GateResult:
src_exts = SOURCE_EXTS.get(repo, set())
test_dirs = TEST_DIRS.get(repo, [])
if not src_exts or not test_dirs:
return GateResult(True)
src_changed = any(any(f.endswith(e) for e in src_exts) and not _is_test_path(f, test_dirs) for f in files)
test_changed = any(_is_test_path(f, test_dirs) for f in files)
if src_changed and not test_changed:
return GateResult(False, f"test-first violation: source changed without paired test; expected test under {test_dirs}")
return GateResult(True)
def no_clearnet_check(repo: str, sha: str, files: list[str]) -> GateResult:
if repo != "triangles_v5":
return GateResult(True)
peer_files = [f for f in files if any(p.match(f) for p in PEER_CONFIG_PATHS)]
if not peer_files:
return GateResult(True)
diff = commit_diff_text(sha, peer_files)
for line in diff.split("\n"):
if not line.startswith("+") or line.startswith("+++"):
continue
body = line[1:].strip()
if re.search(r"\b(addnode|seednode|connect)\s*=", body, re.IGNORECASE):
if ".onion" not in body.lower():
return GateResult(False, f"no-clearnet: added peer/seed without .onion: {body[:120]}")
if re.match(r"^\s*(\d{1,3}\.){3}\d{1,3}\b", body) or re.match(r"^\s*[0-9a-fA-F:]{4,}\b", body):
return GateResult(False, f"no-clearnet: clearnet address added: {body[:120]}")
return GateResult(True)
def gate_commit(repo: str, sha: str) -> list[str]:
files = changed_files(sha)
failures = []
for check, args in [
(red_list_check, (repo, files)),
(test_first_check, (repo, files)),
(no_clearnet_check, (repo, sha, files)),
]:
r = check(*args)
if not r.ok:
failures.append(f"commit {sha[:12]}: {r.reason}")
return failures
def emit_output(name: str, value: str):
out_file = os.environ.get("GITHUB_OUTPUT", "")
if out_file:
with open(out_file, "a") as fh:
fh.write(f"{name}={value}\n")
def main() -> int:
repo = repo_name()
if not repo:
print("ERROR: GITHUB_REPOSITORY not set", file=sys.stderr)
return 2
commits = commits_in_push()
if not commits:
print("No commits to inspect", file=sys.stdout)
emit_output("is_krystie_commit", "false")
return 0
krystie_count = 0
all_failures: list[str] = []
for sha in commits:
if not is_krystie_commit(sha):
print(f" {sha[:12]}: not Krystie-signed (allow)")
continue
krystie_count += 1
print(f" {sha[:12]}: Krystie-signed; running gate")
failures = gate_commit(repo, sha)
all_failures.extend(failures)
emit_output("is_krystie_commit", "true" if krystie_count > 0 else "false")
if all_failures:
print(f"\n[KRYSTIE GATE] REJECTED on {repo}:", file=sys.stderr)
for f in all_failures:
print(f" - {f}", file=sys.stderr)
return 1
print(f"[KRYSTIE GATE] PASS on {repo} ({krystie_count} Krystie commit(s) inspected, {len(commits) - krystie_count} non-Krystie)")
return 0
if __name__ == "__main__":
sys.exit(main())
+132
View File
@@ -0,0 +1,132 @@
name: Krystie Gate
# Runs on every push to krystie-wip/* branches.
# Static checks first (cheap), then build + tests.
# If everything green AND the commit is Krystie's, fast-forwards master.
# Sami's pushes (admin) bypass this entire flow — he goes direct to master.
on:
push:
branches:
- 'krystie-wip/**'
jobs:
static-gate:
name: "Static gate (red-list / test-first / no-clearnet)"
runs-on: ubuntu-latest
outputs:
is_krystie_commit: ${{ steps.gate.outputs.is_krystie_commit }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Import Krystie public key (for verification)
run: |
mkdir -p ~/.gnupg && chmod 700 ~/.gnupg
if [ -f .gitea/krystie-release.pub.asc ]; then
gpg --import .gitea/krystie-release.pub.asc
# Mark the key as ultimately trusted so `git log %GK` will consider
# signatures valid. Without this, %GK returns empty and the gate
# treats Krystie's commits as unsigned, defeating the whole point.
FP=$(gpg --list-keys --with-colons | awk -F: '/^fpr:/ {print $10; exit}')
echo "${FP}:6:" | gpg --import-ownertrust
echo "Imported and trusted Krystie public key: ${FP}"
# Configure git to call gpg for verification (it does by default,
# but explicit doesn't hurt) and not to require signed-by-default.
git config --global gpg.program gpg
else
echo "WARN: .gitea/krystie-release.pub.asc not found — gate will treat all commits as non-Krystie (i.e. allow)"
fi
- name: Run gate
id: gate
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_SHA: ${{ github.sha }}
GITHUB_BEFORE: ${{ github.event.before }}
run: |
python3 .gitea/krystie_gate.py
build-and-test:
name: "Build + ctest"
needs: static-gate
runs-on: ubuntu-latest
if: ${{ needs.static-gate.result == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential cmake ninja-build pkg-config \
libssl-dev libboost-all-dev libdb++-dev libleveldb-dev \
librocksdb-dev libevent-dev libsodium-dev \
libsecp256k1-dev || true
# Some packages may not be available; the C++20 / RocksDB modernization
# is in flight, so missing deps are tolerable for v1 of the gate.
- name: Configure (daemon-only, no Qt)
run: |
mkdir -p build && cd build
cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_QT=OFF \
-DBUILD_TESTS=ON \
-DBUILD_ROCKSDB=OFF \
|| (echo "::warning::CMake configure failed — likely WIP modernization. Allowing build skip for v1." && exit 0)
- name: Build
run: |
if [ -f build/build.ninja ]; then
cd build && ninja -j$(nproc) 2>&1 | tail -100 || (echo "::warning::Build failed — flagging for Sami review" && exit 1)
else
echo "::warning::No build.ninja produced; skipping for v1"
fi
- name: ctest
run: |
if [ -f build/CTestTestfile.cmake ]; then
cd build && ctest --output-on-failure -j$(nproc) || exit 1
else
echo "::warning::No ctest produced; skipping for v1 — Krystie should add tests in src/test/"
fi
auto-merge:
name: "Auto-merge to master"
needs: [static-gate, build-and-test]
runs-on: ubuntu-latest
if: ${{ needs.static-gate.result == 'success' && needs.build-and-test.result == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.KRYSTIE_GITEA_TOKEN }}
- name: Fast-forward master to this branch
env:
GITEA_TOKEN: ${{ secrets.KRYSTIE_GITEA_TOKEN }}
BRANCH: ${{ github.ref_name }}
SHA: ${{ github.sha }}
run: |
set -euo pipefail
# The wip branch is master + N Krystie commits. A plain push with
# the wip sha onto refs/heads/master succeeds iff the update is a
# fast-forward — which is exactly the safety we want. (Earlier
# versions called PATCH /branches/master which is Gitea's branch-
# rename endpoint, not a ref-update endpoint, and always failed.)
REPO="${GITHUB_REPOSITORY}" # owner/name
GIT_URL="http://localhost:3030/${REPO}.git"
git -c "http.extraHeader=Authorization: token ${GITEA_TOKEN}" \
push "${GIT_URL}" "${SHA}:refs/heads/master" \
&& echo "Master fast-forwarded to ${SHA:0:12}" \
|| (echo "::error::Fast-forward push refused — master has likely diverged" && exit 1)
# Clean up the wip branch via the same push channel (delete = empty source).
git -c "http.extraHeader=Authorization: token ${GITEA_TOKEN}" \
push "${GIT_URL}" ":refs/heads/${BRANCH}" \
&& echo "Cleaned up wip branch ${BRANCH}" \
|| echo "::warning::Could not delete wip branch (it'll get pruned later)"
+7
View File
@@ -71,3 +71,10 @@ triangles.conf
*.o
src/trianglesd
src/obj/
build-bench/
build-cmake/
build-cmake-test/
build-latest/
build-rocks-probe/
build-rocksdb/
bench-results.csv
+159
View File
@@ -0,0 +1,159 @@
# TRI v6 Development Task Queue
*Autonomous development pipeline — Krystie cycles through these continuously.*
## Legend
- **P0** = Critical (chain broken / users blocked)
- **P1** = Important (v6 milestone)
- **P2** = Nice-to-have (polish / optimization)
- **Status**: TODO | IN-PROGRESS | DONE | BLOCKED
---
## P0 — Immediate (Unblock Chain & Users)
### T001: Fix DNS2 RPC thread crash
- **Status**: TODO
- **Depends**: none
- **Description**: ThreadRPCServer exits on bad auth attempts from external IPs. Need to not kill the RPC thread on individual auth failures.
- **Files**: `src/rpc.cpp` or `src/bitcoinrpc.cpp`
- **Acceptance**: RPC stays up even with bad auth attempts; curl JSON-RPC works reliably
- **Model**: Claude Code or MiniMax M2.7
### T002: Fix DNS2 wallet 0 confirmed balance
- **Status**: TODO
- **Depends**: T001 (need reliable RPC)
- **Description**: Wallet restored from April 20 backup. Shows 11.24 TRI unconfirmed. Need to verify rescan completes and coins mature (520 confirmations) for staking.
- **Files**: wallet.dat, `src/wallet.cpp`
- **Acceptance**: Wallet shows confirmed balance after rescan + confirmations
- **Model**: Krystie (manual investigation, not subagent)
### T003: Fix seeds.txt parsing (only returns 1 address)
- **Status**: TODO
- **Depends**: none
- **Description**: HTTPS fetch of seeds.cryptographic-triangles.org/seeds.txt only returns 1 address. Possible comment parsing bug in net.cpp seed fetch logic.
- **Files**: `src/net.cpp`, `/var/www/seeds/seeds.txt`
- **Acceptance**: All 7 onion addresses returned on fetch
- **Model**: ZAI GLM-5.1
### T004: Fix Sami's PC wallet block 570 stall
- **Status**: IN-PROGRESS
- **Depends**: Windows binary build (DONE — built on sami-pc)
- **Description**: Windows Qt wallet stuck at block 570. GUI bootstrap fix committed (d0fb2dc). New binary built at E:\repos\triangles_v5\build-mingw\bin\triangles-qt.exe. Needs testing.
- **Acceptance**: Windows wallet syncs past block 570 with bootstrap
- **Model**: Krystie (manual deployment)
---
## P1 — v6 Core Milestones
### T010: Complete RocksDB runtime testing
- **Status**: TODO
- **Depends**: T001
- **Description**: RocksDB backend compiles clean but never tested with actual blockchain data. Need to: start daemon with `-rocksdb`, let it index chain, verify block lookups work, compare performance vs LevelDB.
- **Files**: `src/txdb.h`, `src/txdb.cpp`, `src/utxosnapshot.cpp`
- **Acceptance**: Daemon runs with `-rocksdb` flag, processes blocks, RPC queries return correct data
- **Model**: MiniMax M2.7
### T011: Wire UTXO snapshot P2P distribution (SnapshotNet)
- **Status**: TODO
- **Depends**: T010
- **Description**: `snapshotnet.cpp` exists but is placeholder. Need to implement: peer advertisement of snapshot availability, chunk transfer protocol, hash verification, integration with bootstrap flow.
- **Files**: `src/snapshotnet.cpp`, `src/net.cpp`, `src/utxosnapshot.cpp`
- **Acceptance**: New node can get UTXO snapshot from peers via P2P (not just HTTPS)
- **Model**: Claude Code + MiniMax M2.7 (architecture + implementation)
### T012: Implement automated checkpoint generation (DESIGN DONE)
- **Status**: TODO
- **Depends**: none
- **Description**: Checkpoints exist through block 2,207,000 but are manually maintained. Need automated checkpoint generation: every N blocks, compute checkpoint hash, push to code or external manifest.
- **Files**: `src/checkpoints.cpp`, `src/checkpoints.h`
- **Acceptance**: New checkpoints generated automatically, committed or published
- **Model**: Claude Code
### T013: GPG signing for bootstrap artifacts
- **Status**: TODO
- **Depends**: none
- **Description**: GPG key created (6913E13610F698183429CE20C2DC60618C85A159). Need to: sign every bootstrap/snapshot artifact on generation, verify signature on download, publish public key.
- **Files**: `/usr/local/bin/auto-update.sh`, `src/bootstrap.cpp`
- **Acceptance**: `gpg --verify` works on downloaded artifacts
- **Model**: ZAI GLM-5.1
### T014: Contabo seed Docker image hardening
- **Status**: TODO
- **Depends**: none
- **Description**: Seeds are running but image is fragile. Need: proper Dockerfile with version pinning, health checks, auto-restart, log shipping, and persistent volumes.
- **Files**: `/tmp/Dockerfile` on Contabo, `/tri/seed-{1..4}/`
- **Acceptance**: Seeds survive host reboot, auto-restart on crash, health check endpoint
- **Model**: ZAI GLM-5.1
### T015: Network health dashboard
- **Status**: TODO
- **Depends**: T001, T003
- **Description**: Operator-facing dashboard showing: block height per node, peer count, staking weight, chain sync status, seed health. Could be a simple web page served from DNS2.
- **Files**: New — `src/rpcblockchain.cpp` (health endpoint), frontend
- **Acceptance**: Live page showing all 7 nodes' status updated every 30s
- **Model**: MiniMax M2.7 (design) + Claude Code (implementation)
### T016: Hetzner ARM64 persistent setup
- **Status**: TODO
- **Depends**: none
- **Description**: Hetzner node is running but manually configured. Need: systemd service, auto-start on boot, bootstrap automation, monitoring.
- **Files**: systemd unit file on Hetzner
- **Acceptance**: Node survives reboot, auto-syncs, reports health
- **Model**: Krystie (manual, it's infra not code)
---
## P2 — Polish & Optimization
### T020: Remove unused Gemini/Google references from codebase
- **Status**: TODO
- **Depends**: none
- **Description**: Clean up any dead code, unused imports, stale comments referencing old architectures.
- **Model**: ZAI GLM-5.1
### T021: Comprehensive test suite
- **Status**: TODO
- **Depends**: T010
- **Description**: Expand test coverage for: UTXO snapshot load/dump, RocksDB backend, bootstrap download, seed fetch, checkpoint verification.
- **Files**: `src/test/`
- **Acceptance**: `test_triangles` passes with < 5 pre-existing failures
- **Model**: ZAI GLM-5.1 + MiniMax M2.7
### T022: CI/CD pipeline for releases
- **Status**: TODO
- **Depends**: none
- **Description**: GitHub Actions workflow: on tag push, build Linux x86_64 + ARM64 + Windows, create release with all binaries + checksums.
- **Files**: `.github/workflows/build-all.yml`
- **Acceptance**: Tag push produces release with 3 platform binaries
- **Model**: ZAI GLM-5.1
### T023: TRIdock + tri-wallet-web consolidation
- **Status**: TODO
- **Depends**: none
- **Description**: TRIdock and tri-wallet-web appear to be near-duplicates. Evaluate and either consolidate or clearly separate concerns.
- **Model**: MiniMax M2.7 (analysis)
---
## Completed
### ✅ Windows GUI bootstrap fix (d0fb2dc)
- Removed `#ifndef QT_GUI` guard so auto-bootstrap runs in GUI wallet
- Added `uiInterface.InitMessage()` for progress display
### ✅ Windows native build on sami-pc
- Built `triangles-qt.exe` (26MB) and `trianglesd.exe` via MSYS2/MinGW64
- All dependencies found natively
### ✅ RocksDB integration complete (ac9c6fb)
- CActiveTxDB wrapper, dual-backend support, compiles clean
### ✅ All nodes updated to v5.9.7.0
- DNS2, DNS3, Hetzner, Contabo seeds all running latest
### ✅ Bootstrap infrastructure live
- HTTPS at bootstrap.cryptographic-triangles.org
- Tor hidden service serving nginx on port 8085
- Seeds.txt with 7 onion nodes
+7
View File
@@ -0,0 +1,7 @@
# Krystie runner log
This file records autonomous-runner activity. Each entry is a doc-only
edit produced by the demo worker; once OpenClaw is wired in this log
will be replaced by real work.
- [2026-04-29T06:57:30Z] triangles_v5#1 — Smoke-test the Krystie loop runner
+210
View File
@@ -0,0 +1,210 @@
#!/usr/bin/env bash
set -euo pipefail
# Fresh-datadir IBD smoke test for TRI.
# Goal: detect the classic "starts from zero but stalls early / loops around 570"
# failure mode, and verify that sync keeps making forward progress.
#
# Example:
# bash scripts/ibd-smoke-test.sh \
# --bin ./build/src/trianglesd \
# --bootstrap-url http://100.104.4.5:8085/triangles-bootstrap.tar.gz \
# --addnode 74.208.167.19 --addnode 194.233.88.206
BIN="${BIN:-./build/src/trianglesd}"
TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-1800}" # 30 minutes target window
POLL_SECONDS="${POLL_SECONDS:-15}"
STALL_WINDOW_SECONDS="${STALL_WINDOW_SECONDS:-180}"
BOOTSTRAP_URL="${BOOTSTRAP_URL:-}"
WORKDIR="${WORKDIR:-}"
RPC_PORT="${RPC_PORT:-19192}"
P2P_PORT="${P2P_PORT:-24193}"
MIN_EXPECTED_HEIGHT="${MIN_EXPECTED_HEIGHT:-5000}"
ALLOW_IBD="${ALLOW_IBD:-0}"
WHITELIST="${WHITELIST:-127.0.0.1}"
ADDNODES=()
usage() {
cat <<EOF
Usage: $0 [options]
Options:
--bin PATH trianglesd binary (default: $BIN)
--bootstrap-url URL optional bootstrap tar.gz URL to preload
--workdir PATH use an explicit temp workdir
--rpc-port N RPC port for test node (default: $RPC_PORT)
--p2p-port N P2P port for test node (default: $P2P_PORT)
--timeout N total test timeout seconds (default: $TIMEOUT_SECONDS)
--poll N poll interval seconds (default: $POLL_SECONDS)
--stall-window N no-progress failure window seconds (default: $STALL_WINDOW_SECONDS)
--min-height N minimum expected height/progress floor (default: $MIN_EXPECTED_HEIGHT)
--allow-ibd allow test to pass while still in IBD if progress is strong
--addnode HOST trusted peer to add (repeatable)
-h, --help show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--bin) BIN="$2"; shift 2 ;;
--bootstrap-url) BOOTSTRAP_URL="$2"; shift 2 ;;
--workdir) WORKDIR="$2"; shift 2 ;;
--rpc-port) RPC_PORT="$2"; shift 2 ;;
--p2p-port) P2P_PORT="$2"; shift 2 ;;
--timeout) TIMEOUT_SECONDS="$2"; shift 2 ;;
--poll) POLL_SECONDS="$2"; shift 2 ;;
--stall-window) STALL_WINDOW_SECONDS="$2"; shift 2 ;;
--min-height) MIN_EXPECTED_HEIGHT="$2"; shift 2 ;;
--allow-ibd) ALLOW_IBD=1; shift ;;
--addnode) ADDNODES+=("$2"); shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "unknown arg: $1" >&2; usage; exit 2 ;;
esac
done
if [[ ! -x "$BIN" ]]; then
echo "ERROR: trianglesd binary not executable: $BIN" >&2
exit 2
fi
if [[ -z "$WORKDIR" ]]; then
WORKDIR="$(mktemp -d /tmp/tri-ibd-smoke-XXXXXX)"
fi
DATADIR="$WORKDIR/datadir"
mkdir -p "$DATADIR"
RPCUSER="tri_test"
RPCPASSWORD="tri_test_$(date +%s)_$RANDOM"
CONF="$DATADIR/triangles.conf"
cat > "$CONF" <<EOF
server=1
daemon=1
staking=0
listen=1
discover=0
upnp=0
tor=0
irc=0
dnsseed=1
checkpoints=1
rpcuser=$RPCUSER
rpcpassword=$RPCPASSWORD
rpcport=$RPC_PORT
port=$P2P_PORT
maxconnections=32
whitelist=$WHITELIST
logtimestamps=1
EOF
for host in "${ADDNODES[@]}"; do
echo "addnode=$host" >> "$CONF"
done
cleanup() {
"$BIN" -datadir="$DATADIR" -conf="$CONF" stop >/dev/null 2>&1 || true
sleep 2 || true
pkill -f "$DATADIR" >/dev/null 2>&1 || true
}
trap cleanup EXIT
if [[ -n "$BOOTSTRAP_URL" ]]; then
echo "[ibd-test] downloading bootstrap: $BOOTSTRAP_URL"
curl -L --fail --max-time 1800 "$BOOTSTRAP_URL" -o "$WORKDIR/bootstrap.tar.gz"
tar xzf "$WORKDIR/bootstrap.tar.gz" -C "$DATADIR"
rm -f "$DATADIR/database/log."* "$DATADIR/txleveldb/LOCK" "$DATADIR/smsgDB/LOCK" 2>/dev/null || true
fi
echo "[ibd-test] starting node from datadir: $DATADIR"
"$BIN" -daemon -datadir="$DATADIR" -conf="$CONF" >/dev/null
sleep 6
rpc() {
local method="$1"
local params="${2:-[]}"
curl -sS --fail --user "$RPCUSER:$RPCPASSWORD" \
--data-binary "{\"jsonrpc\":\"1.0\",\"id\":\"ibd\",\"method\":\"$method\",\"params\":$params}" \
-H 'content-type: text/plain;' "http://127.0.0.1:$RPC_PORT/"
}
extract_json() {
python3 -c 'import json,sys; obj=json.load(sys.stdin); print(obj["result"])'
}
extract_field() {
local field="$1"
python3 -c 'import json,sys; obj=json.load(sys.stdin); val=obj["result"].get(sys.argv[1]); print(val if val is not None else "")' "$field"
}
start_ts=$(date +%s)
last_progress_ts=$start_ts
last_height=-1
samples=0
same_570_loops=0
best_height=0
while true; do
now=$(date +%s)
elapsed=$((now - start_ts))
if (( elapsed > TIMEOUT_SECONDS )); then
echo "FAIL: timeout after ${elapsed}s"
break
fi
if info_json="$(rpc getblockchaininfo 2>/dev/null)"; then
height=$(printf '%s' "$info_json" | extract_field blocks)
ibd=$(printf '%s' "$info_json" | extract_field initialblockdownload)
headers=$(printf '%s' "$info_json" | extract_field headers)
else
height=""
ibd=""
headers=""
fi
peers=0
if peer_json="$(rpc getconnectioncount 2>/dev/null)"; then
peers=$(printf '%s' "$peer_json" | extract_json)
fi
if [[ -n "$height" && "$height" != "$last_height" ]]; then
last_progress_ts=$now
last_height="$height"
if (( height > best_height )); then
best_height=$height
fi
fi
log_file="$DATADIR/debug.log"
if [[ -f "$log_file" ]]; then
loop_hits=$(tail -n 400 "$log_file" | grep -c 'start=571' || true)
if (( loop_hits >= 3 )); then
same_570_loops=$loop_hits
fi
fi
echo "[ibd-test] t=${elapsed}s height=${height:-?} headers=${headers:-?} ibd=${ibd:-?} peers=$peers best=$best_height"
if [[ -n "$height" ]] && (( best_height >= MIN_EXPECTED_HEIGHT )) && [[ "$ibd" == "False" || "$ibd" == "false" ]]; then
echo "PASS: left IBD and reached height $best_height"
exit 0
fi
if [[ "$ALLOW_IBD" == "1" && -n "$height" ]] && (( best_height >= MIN_EXPECTED_HEIGHT )); then
echo "PASS: strong sync progress observed (height $best_height) even though IBD remains true"
exit 0
fi
if (( now - last_progress_ts > STALL_WINDOW_SECONDS )); then
echo "FAIL: no block-height progress for $((now - last_progress_ts))s"
if (( same_570_loops > 0 )); then
echo "HINT: detected repeated start=571 loop pattern ($same_570_loops hits in recent log tail)"
fi
echo "--- debug tail ---"
tail -n 120 "$log_file" 2>/dev/null || true
exit 1
fi
((samples++)) || true
sleep "$POLL_SECONDS"
done
exit 1
+6 -2
View File
@@ -25,13 +25,17 @@ namespace Checkpoints
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
{ 3935, uint256("0xe16290c9757d1368b8d7c35de4f8f70c2c9f9c785b667df0c3bff85086ca6")},
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
// Recent finality pin (PoS era). Closes the long unchecked span from
// 17650 to the live tip so stale-bootstrap / low-trust forks below
// this height are rejected outright. Hash from the canonical chain.
{ 2205000, uint256("0x6bdd3c5e5a32e1dd9a70e705f1a28d1dd84929f89579bd2696d41bc87f39446f")},
};
// Published UTXO snapshot file SHA256, keyed by snapshot height.
@@ -54,7 +58,7 @@ namespace Checkpoints
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
{ 3935, uint256("0xe16290c9757d1368b8d7c35de4f8f70c2c9f9c785b667df0c3bff85086ca6")},
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
+3 -3
View File
@@ -6,9 +6,9 @@
//
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 6
#define CLIENT_VERSION_MINOR 0
#define CLIENT_VERSION_REVISION 0
#define CLIENT_VERSION_MAJOR 5
#define CLIENT_VERSION_MINOR 9
#define CLIENT_VERSION_REVISION 12
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
+25 -4
View File
@@ -417,7 +417,7 @@ std::string HelpMessage()
//" -proxy=<ip:port> " + _("Connect through socks proxy") + "\n" +
//" -socks=<n> " + _("Select the version of socks proxy to use (4-5, default: 5)") + "\n" +
" -tor=<ip:port> " + _("Use proxy to reach tor hidden services (default: same as -proxy)") + "\n"
" -notor " + _("Disable Tor (WARNING: wallet will not start - Tor is required)") + "\n" +
" -notor " + _("Disable Tor - run in clearnet-only mode (no .onion connectivity)") + "\n" +
" -torsocks=<port> " + _("Set embedded or managed Tor SOCKS proxy port (default: 19099)") + "\n" +
" -torhiddenservice " + _("Enable the managed Tor hidden service (default: 1)") + "\n" +
" -torhsport=<port> " + _("Set embedded or managed Tor hidden service port (default: wallet listen port)") + "\n" +
@@ -927,7 +927,8 @@ bool AppInit2()
// v5.9.5: P2P UTXO snapshot fetch is the default for fresh installs (Step 11.6).
// The legacy clearnet HTTP bootstrap only runs when the user explicitly requests
// it via -bootstrap, or when -snapshot=0 disables the P2P fetcher.
#ifndef QT_GUI
// Bootstrap auto-download works for both GUI and daemon.
// GUI users get the same automatic bootstrap on fresh installs.
{
bool wantsBootstrap = GetBoolArg("-bootstrap", false);
bool noBootstrap = GetBoolArg("-nobootstrap", false);
@@ -938,6 +939,7 @@ bool AppInit2()
if (needsBootstrap && !noBootstrap && !snapshotMode) {
printf("Bootstrap: no blockchain data found — downloading automatically.\n");
printf("Bootstrap: (use -nobootstrap to skip)\n");
uiInterface.InitMessage(_("Downloading blockchain data..."));
wantsBootstrap = true;
} else if (needsBootstrap && snapshotMode && !wantsBootstrap) {
printf("Bootstrap: no blockchain data found — will fetch UTXO snapshot via P2P after network start.\n");
@@ -951,13 +953,24 @@ bool AppInit2()
std::string host = Bootstrap::DEFAULT_HOST;
std::string strError;
auto progressFn = [](int64_t bytesDownloaded, int64_t totalBytes) {
int64_t lastGuiUpdate = 0;
auto progressFn = [&lastGuiUpdate](int64_t bytesDownloaded, int64_t totalBytes) {
if (totalBytes > 0) {
printf("\rBootstrap: %lld / %lld MB (%lld%%)",
(long long)(bytesDownloaded / (1024*1024)),
(long long)(totalBytes / (1024*1024)),
(long long)((bytesDownloaded * 100) / totalBytes));
fflush(stdout);
// Update GUI status bar every ~1 MB
int64_t now = GetTimeMillis();
if (now - lastGuiUpdate > 1000) {
lastGuiUpdate = now;
std::string msg = strprintf("Downloading blockchain: %lld / %lld MB (%lld%%)",
(long long)(bytesDownloaded / (1024*1024)),
(long long)(totalBytes / (1024*1024)),
(long long)((bytesDownloaded * 100) / totalBytes));
uiInterface.InitMessage(msg);
}
}
};
@@ -999,7 +1012,6 @@ bool AppInit2()
strprintf("host=%s success=%d utxo_snapshot=%d", host.c_str(), success, triedUtxoSnapshot));
}
} // end bootstrap scope
#endif
// ********************************************************* Step 6c: manual UTXO snapshot loading
// If utxo-snapshot.bin exists in data dir and the chain DB hasn't been
@@ -1343,6 +1355,15 @@ bool AppInit2()
#ifdef USE_UPNP
fUseUPnP = false;
#endif
} else if (GetBoolArg("-notor", false)) {
// -notor: user explicitly disabled Tor. Allow the daemon to start
// in clearnet-only mode (useful for diagnostics, benchmarking, and
// recovery). .onion connectivity will not be available.
printf("NOTICE: Tor disabled via -notor. Running in clearnet-only mode.\n");
printf(" .onion connections will NOT be available.\n");
SetReachable(NET_IPV4, true);
SetReachable(NET_IPV6, true);
SetReachable(NET_TOR, false);
} else {
std::string torError = CTorEmbedded::GetInstance()->GetStartupError();
if (torError.empty())
+11 -1
View File
@@ -36,8 +36,18 @@ int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd)
// comes back with massively amplified staking power and dominates blocks.
// The 7-day cap still allows generous accumulation while limiting abuse.
static const int64_t STAKE_AGE_SOFT_CAP = 7 * 24 * 60 * 60; // 7 days
// Activation gate: the soft cap shipped 2026-04-20 without a height/time
// gate, retroactively invalidating earlier blocks staked with long-aged
// coins (e.g. coins idle through the 2022-2026 freeze). Apply the cap
// only to stakes after the activation timestamp; historical stakes
// validate under the rules they were created with (uncapped age).
static const int64_t STAKE_AGE_SOFT_CAP_ACTIVATION = 1776000000; // 2026-04-12 ~13:20 UTC
if (pindexBest && pindexBest->nHeight >= FORK_HEIGHT_V5)
return min(nAge, STAKE_AGE_SOFT_CAP);
{
if (nIntervalEnd >= STAKE_AGE_SOFT_CAP_ACTIVATION)
return min(nAge, STAKE_AGE_SOFT_CAP);
return nAge;
}
return min(nAge, (int64_t)nStakeMaxAge);
}
+45 -21
View File
@@ -1324,10 +1324,12 @@ bool IsInitialBlockDownload()
pindexLastBest = pindexBest;
nLastUpdate = GetTime();
}
// IBD is complete once we've passed the checkpoint height estimate.
// The previous >24h block-time check incorrectly kept IBD true when the
// chain was synced but simply stalled (no new blocks arriving), which
// prevented the stake miner from ever proceeding.
// IBD is complete once we've passed the checkpoint AND the chain tip is
// recent (within 24h). This prevents a stall AFTER checkpoint from
// permanently disabling header fetching. The forcestaking path above
// handles the specific staking-broker scenario.
if (GetTime() - nLastUpdate > 24 * 60 * 60)
return true;
return false;
}
@@ -2820,7 +2822,12 @@ bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot, bool fCheckSig) c
return DoS(100, error("CheckBlock() : more than one coinbase"));
// Check coinbase timestamp
if (GetBlockTime() > FutureDrift((int64_t)vtx[0].nTime))
// NOTE: Must use the pre-fork (10-minute) drift tolerance here because
// CheckBlock() is context-free (no nHeight) and can be called on blocks
// at any height, including during chain reorgs from old fork chains.
// The tight 90-second drift for post-fork blocks is enforced separately
// in AcceptBlock/ConnectBlock with proper height context.
if (GetBlockTime() > (int64_t)vtx[0].nTime + 10 * 60)
return DoS(50, error("CheckBlock() : coinbase timestamp is too early"));
if (IsProofOfStake())
@@ -3064,7 +3071,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
// triangles: check proof-of-stake
// Limited duplicity on stake: prevents block flood attack
// Duplicate stake allowed only when there is orphan child block
if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
if (pblock->IsProofOfStake() && !GetBoolArg("-ignoredupstake", false) && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str());
// Preliminary checks
@@ -3122,13 +3129,15 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
{
// Limited duplicity on stake: prevents block flood attack
// Duplicate stake allowed only when there is orphan child block
if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
if (!GetBoolArg("-ignoredupstake", false) && setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock2->GetProofOfStake().first.ToString().c_str(), pblock2->GetProofOfStake().second, hash.ToString().c_str());
else
setStakeSeenOrphan.insert(pblock2->GetProofOfStake());
}
uint256 hashPrevOrphan = pblock2->hashPrevBlock;
CBlock* pblock2raw = pblock2.get();
mapOrphanBlocks.insert(make_pair(hash, std::move(pblock2)));
mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2.get()));
mapOrphanBlocksByPrev.insert(make_pair(hashPrevOrphan, pblock2raw));
dequeOrphanOrder.push_back(hash); // track insertion order for FIFO eviction
// Limit orphan blocks to prevent memory exhaustion.
@@ -3140,11 +3149,11 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
// Ask this guy to fill in what we're missing
if (pfrom && pindexBest)
{
pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2.get()));
pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2raw));
// triangles: getblocks may not obtain the ancestor block rejected
// earlier by duplicate-stake check so we ask for it again directly
if (!IsInitialBlockDownload())
pfrom->AskFor(CInv(MSG_BLOCK, WantedByOrphan(pblock2.get())));
pfrom->AskFor(CInv(MSG_BLOCK, WantedByOrphan(pblock2raw)));
}
return true;
}
@@ -4043,14 +4052,23 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
// parallelism. Multiple peers sending overlapping inv ranges is harmless
// (AlreadyHave filters duplicates) but ensures we discover and download
// blocks from the fastest available source.
// NOTE: nStartingHeight from version messages is unverified. Peers can
// claim any height. During IBD we always ask all eligible peers rather
// than filtering on a claim that may be wrong (a stunted node could be
// reporting the full chain height while only serving the tail of its
// own fork). Use nBestKnownHeight (updated from actual block responses)
// for peer capability assessment instead.
static int nAskedForBlocks = 0;
bool fIBD = IsInitialBlockDownload();
bool fBehindPeer = (pfrom->nStartingHeight > nBestHeight);
// During IBD: ask every non-client peer unconditionally to maximise
// download sources. Post-IBD: use traditional height-check logic.
bool fShouldAsk = !pfrom->fClient && !pfrom->fOneShot &&
(pfrom->nStartingHeight > (nBestHeight - 144)) &&
(fIBD ||
pfrom->nStartingHeight > (nBestHeight - 144) ||
pfrom->nStartingHeight > nBestHeight) &&
(pfrom->nVersion < NOBLKS_VERSION_START ||
pfrom->nVersion >= NOBLKS_VERSION_END) &&
(fIBD || nAskedForBlocks < 1 || vNodes.size() <= 1 || fBehindPeer);
(fIBD || nAskedForBlocks < 1 || vNodes.size() <= 1 || pfrom->nStartingHeight > nBestHeight);
printf("IBD-DIAG: version handler: peer=%s height=%d ourHeight=%d fClient=%d fOneShot=%d shouldAsk=%d nAskedForBlocks=%d IBD=%d\n",
pfrom->addr.ToString().c_str(), pfrom->nStartingHeight, nBestHeight,
pfrom->fClient, pfrom->fOneShot, fShouldAsk, nAskedForBlocks, fIBD);
@@ -4361,19 +4379,25 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
// GetBlockIndex() fell through to genesis (no locator hash matched
// our main chain). If the peer's tip isn't our genesis,
// they're on a completely different fork.
//
// triangles fix: instead of banning or disconnecting, always respond
// with our main chain blocks so a fork node can learn the canonical
// chain and reorganize. The fork node's client will automatically
// reorg when it receives blocks that form a longer or higher-work chain.
if (!locator.IsNull() && pindex == pindexGenesisBlock &&
pindexGenesisBlock && locator.GetTipHash() != pindexGenesisBlock->GetBlockHash())
{
pfrom->nIncompatibleGetblocks++;
if (pfrom->nIncompatibleGetblocks >= 3)
{
printf("WARNING: peer %s sent %d getblocks with no common blocks — disconnecting (incompatible fork)\n",
pfrom->addr.ToString().c_str(), pfrom->nIncompatibleGetblocks);
pfrom->Misbehaving(100);
return true;
}
printf("WARNING: peer %s getblocks locator has no common blocks (%d/3 before ban)\n",
// triangles: after many failed attempts, reset — the peer may now be
// on the correct chain and we don't want to ban a node that's just
// learning about the main chain from us.
if (pfrom->nIncompatibleGetblocks > 10)
pfrom->nIncompatibleGetblocks = 0;
// triangles: NO return/ban here — fall through and serve main chain
// blocks so the forking peer can reorg to our chain.
printf("WARNING: peer %s getblocks locator has no common blocks — serving main chain from genesis (counter=%d, will reset after 10)\\n",
pfrom->addr.ToString().c_str(), pfrom->nIncompatibleGetblocks);
pindex = pindexGenesisBlock;
}
else if (pindex && pindex != pindexGenesisBlock)
{
+28 -4
View File
@@ -259,10 +259,34 @@ bool CTorProcess::WriteTorrc()
// SOCKS proxy for wallet connections
torrc << "SocksPort " << socksPort << "\n";
// Data directory for Tor state
fs::path torStateDir = dataPath / "state";
fs::create_directories(torStateDir);
torrc << "DataDirectory " << torStateDir.string() << "\n";
// Data directory for Tor state.
// Use the tor_data directory itself as DataDirectory so that Tor creates
// its internal 'state' FILE at <tor_data>/state. Older wallet builds
// erroneously created a subdirectory called 'state' and pointed
// DataDirectory at it; newer Tor versions (0.4.9+) reject that because
// they expect to write a plain file called 'state' inside DataDirectory.
//
// Recovery: if 'state' exists as a directory, move its contents up and
// remove it so that Tor can create its state file in the normal location.
{
fs::path badStateDir = dataPath / "state";
if (fs::exists(badStateDir) && fs::is_directory(badStateDir)) {
// Migrate any files inside the bad 'state/' directory up to dataPath
try {
for (auto& entry : fs::directory_iterator(badStateDir)) {
fs::path dest = dataPath / entry.path().filename();
if (!fs::exists(dest)) {
fs::rename(entry.path(), dest);
}
}
fs::remove(badStateDir);
printf("Auto-recovered: removed legacy 'state' directory from %s\n", dataPath.string().c_str());
} catch (const fs::filesystem_error& e) {
printf("WARNING: Could not auto-recover tor_data/state directory: %s\n", e.what());
}
}
}
torrc << "DataDirectory " << dataPath.string() << "\n";
// Persistent Tor log for post-mortem debugging on user machines.
fs::path torLogPath = dataPath / "tor.log";
+66 -34
View File
@@ -546,10 +546,17 @@ int ReadHTTP(std::basic_istream<char>& stream, map<string, string>& mapHeadersRe
bool HTTPAuthorized(map<string, string>& mapHeaders)
{
string strAuth = mapHeaders["authorization"];
if (strAuth.substr(0,6) != "Basic ")
if (strAuth.size() < 6 || strAuth.substr(0,6) != "Basic ")
return false;
string strUserPass64 = strAuth.substr(6); strUserPass64 = TrimString(strUserPass64);
string strUserPass = DecodeBase64(strUserPass64);
if (strUserPass64.empty())
return false;
string strUserPass;
try {
strUserPass = DecodeBase64(strUserPass64);
} catch (const std::exception&) {
return false;
}
return TimingResistantEqual(strUserPass, strRPCUserColonPass);
}
@@ -783,44 +790,61 @@ static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol,
{
vnThreadsRunning[THREAD_RPCLISTENER]++;
// Immediately start accepting new connections, except when we're cancelled or our socket is closed.
if (error != asio::error::operation_aborted
&& acceptor->is_open())
RPCListen(acceptor, context, fUseSSL);
try {
// Immediately start accepting new connections, except when we're cancelled or our socket is closed.
if (error != asio::error::operation_aborted
&& acceptor->is_open())
RPCListen(acceptor, context, fUseSSL);
AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn);
AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn);
if (error)
{
if (error != asio::error::operation_aborted)
printf("RPC accept error from %s: %s (%d)\n",
tcp_conn ? tcp_conn->peer.address().to_string().c_str() : "unknown peer",
error.message().c_str(),
error.value());
if (error)
{
if (error != asio::error::operation_aborted)
printf("RPC accept error from %s: %s (%d)\n",
tcp_conn ? tcp_conn->peer.address().to_string().c_str() : "unknown peer",
error.message().c_str(),
error.value());
delete conn;
vnThreadsRunning[THREAD_RPCLISTENER]--;
return;
}
// Restrict callers by IP. It is important to
// do this before starting client thread, to filter out
// certain DoS and misbehaving clients.
else if (tcp_conn
&& !ClientAllowed(tcp_conn->peer.address()))
{
// Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake.
try {
if (!fUseSSL)
conn->stream() << HTTPReply(HTTP_FORBIDDEN, "", false) << std::flush;
} catch (const std::exception& e) {
printf("RPC error sending 403 to %s: %s\n",
tcp_conn->peer.address().to_string().c_str(), e.what());
}
delete conn;
vnThreadsRunning[THREAD_RPCLISTENER]--;
return;
}
// start HTTP client thread
else if (!NewThread(ThreadRPCServer3, conn)) {
printf("Failed to create RPC server client thread\n");
delete conn;
}
vnThreadsRunning[THREAD_RPCLISTENER]--;
} catch (std::exception& e) {
PrintException(&e, "RPCAcceptHandler()");
delete conn;
vnThreadsRunning[THREAD_RPCLISTENER]--;
return;
}
// Restrict callers by IP. It is important to
// do this before starting client thread, to filter out
// certain DoS and misbehaving clients.
else if (tcp_conn
&& !ClientAllowed(tcp_conn->peer.address()))
{
// Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake.
if (!fUseSSL)
conn->stream() << HTTPReply(HTTP_FORBIDDEN, "", false) << std::flush;
} catch (...) {
PrintException(NULL, "RPCAcceptHandler()");
delete conn;
vnThreadsRunning[THREAD_RPCLISTENER]--;
}
// start HTTP client thread
else if (!NewThread(ThreadRPCServer3, conn)) {
printf("Failed to create RPC server client thread\n");
delete conn;
}
vnThreadsRunning[THREAD_RPCLISTENER]--;
}
void ThreadRPCServer2(void* parg)
@@ -1128,6 +1152,7 @@ void ThreadRPCServer3(void* parg)
AcceptedConnection *conn = (AcceptedConnection *) parg;
bool fRun = true;
try {
while (true)
{
if (fShutdown || !fRun)
@@ -1247,6 +1272,13 @@ void ThreadRPCServer3(void* parg)
}
}
} // end try
catch (std::exception& e) {
PrintException(&e, "ThreadRPCServer3()");
} catch (...) {
PrintException(NULL, "ThreadRPCServer3()");
}
delete conn;
{
LOCK(cs_THREAD_RPCHANDLER);
+14
View File
@@ -514,6 +514,20 @@ bool CTxDB::LoadBlockIndex()
nBestHeight = pindexBest->nHeight;
nBestChainTrust = pindexBest->nChainTrust;
// Heal pnext pointers along the active chain. Persisted hashNext can be
// stale or zeroed by crash-interrupted reorgs, which breaks
// GetKernelStakeModifier()'s forward walk and causes valid new
// proof-of-stake blocks to be rejected with "check kernel failed".
{
int nHealed = 0;
for (CBlockIndex* p = pindexBest; p && p->pprev; p = p->pprev)
{
if (p->pprev->pnext != p) { p->pprev->pnext = p; nHealed++; }
}
if (nHealed > 0)
printf("LoadBlockIndex(): healed %d pnext links on active chain\n", nHealed);
}
printf("STARTUP-PERF: best_chain %" PRId64 "ms\n", GetTimeMillis() - nPhaseStart);
nPhaseStart = GetTimeMillis();
+14
View File
@@ -542,6 +542,20 @@ bool CRocksTxDB::LoadBlockIndex()
nBestHeight = pindexBest->nHeight;
nBestChainTrust = pindexBest->nChainTrust;
// Heal pnext pointers along the active chain. Persisted hashNext can be
// stale or zeroed by crash-interrupted reorgs, which breaks
// GetKernelStakeModifier()'s forward walk and causes valid new
// proof-of-stake blocks to be rejected with "check kernel failed".
{
int nHealed = 0;
for (CBlockIndex* p = pindexBest; p && p->pprev; p = p->pprev)
{
if (p->pprev->pnext != p) { p->pprev->pnext = p; nHealed++; }
}
if (nHealed > 0)
printf("LoadBlockIndex(): healed %d pnext links on active chain\n", nHealed);
}
printf("STARTUP-PERF: best_chain %" PRId64 "ms\n", GetTimeMillis() - nPhaseStart);
nPhaseStart = GetTimeMillis();