From 5be1ad703fca3c8f0467e934d55747bee7abd2fc Mon Sep 17 00:00:00 2001
From: Joshualover <43139686+Joshualover@users.noreply.github.com>
Date: Sun, 1 Mar 2026 13:39:28 +0800
Subject: [PATCH 01/25] feat: Add rustchain_exporter.py
---
tools/prometheus/rustchain_exporter.py | 198 +++++++++++++++++++++++++
1 file changed, 198 insertions(+)
create mode 100644 tools/prometheus/rustchain_exporter.py
diff --git a/tools/prometheus/rustchain_exporter.py b/tools/prometheus/rustchain_exporter.py
new file mode 100644
index 00000000..603fe91d
--- /dev/null
+++ b/tools/prometheus/rustchain_exporter.py
@@ -0,0 +1,198 @@
+#!/usr/bin/env python3
+"""
+RustChain Prometheus Metrics Exporter
+
+Scrapes RustChain node API and exposes Prometheus-compatible metrics.
+Run with: python rustchain_exporter.py
+"""
+
+import os
+import time
+import logging
+import requests
+from prometheus_client import start_http_server, Gauge, Info, Counter, CollectorRegistry, generate_latest
+
+# Configuration from environment
+NODE_URL = os.environ.get('RUSTCHAIN_NODE_URL', 'https://rustchain.org')
+EXPORTER_PORT = int(os.environ.get('EXPORTER_PORT', 9100))
+SCRAPE_INTERVAL = int(os.environ.get('SCRAPE_INTERVAL', 60))
+
+# Setup logging
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(levelname)s - %(message)s'
+)
+logger = logging.getLogger(__name__)
+
+# Registry for metrics
+registry = CollectorRegistry()
+
+# Node health metrics
+node_up = Gauge('rustchain_node_up', 'Node health status (1=up, 0=down)', ['version'], registry=registry)
+node_uptime = Gauge('rustchain_node_uptime_seconds', 'Node uptime in seconds', registry=registry)
+
+# Miner metrics
+active_miners = Gauge('rustchain_active_miners_total', 'Total number of active miners', registry=registry)
+enrolled_miners = Gauge('rustchain_enrolled_miners_total', 'Total number of enrolled miners', registry=registry)
+miner_last_attest = Gauge('rustchain_miner_last_attest_timestamp', 'Last attestation timestamp per miner', ['miner', 'arch'], registry=registry)
+
+# Epoch metrics
+current_epoch = Gauge('rustchain_current_epoch', 'Current epoch number', registry=registry)
+current_slot = Gauge('rustchain_current_slot', 'Current slot number', registry=registry)
+epoch_slot_progress = Gauge('rustchain_epoch_slot_progress', 'Epoch slot progress (0.0-1.0)', registry=registry)
+epoch_seconds_remaining = Gauge('rustchain_epoch_seconds_remaining', 'Seconds remaining in current epoch', registry=registry)
+
+# Balance metrics
+balance_rtc = Gauge('rustchain_balance_rtc', 'Miner balance in RTC', ['miner'], registry=registry)
+
+# Hall of Fame metrics
+total_machines = Gauge('rustchain_total_machines', 'Total machines in Hall of Fame', registry=registry)
+total_attestations = Gauge('rustchain_total_attestations', 'Total attestations in Hall of Fame', registry=registry)
+oldest_machine_year = Gauge('rustchain_oldest_machine_year', 'Oldest machine year in Hall of Fame', registry=registry)
+highest_rust_score = Gauge('rustchain_highest_rust_score', 'Highest Rust score in Hall of Fame', registry=registry)
+
+# Fee metrics (RIP-301)
+total_fees_collected = Gauge('rustchain_total_fees_collected_rtc', 'Total fees collected in RTC', registry=registry)
+fee_events_total = Counter('rustchain_fee_events_total', 'Total fee events', registry=registry)
+
+
+def fetch_json(url, timeout=10):
+ """Fetch JSON from URL with error handling."""
+ try:
+ response = requests.get(url, timeout=timeout, verify=False) # verify=False for self-signed certs
+ response.raise_for_status()
+ return response.json()
+ except requests.exceptions.RequestException as e:
+ logger.error(f"Failed to fetch {url}: {e}")
+ return None
+
+
+def scrape_health():
+ """Scrape /health endpoint."""
+ data = fetch_json(f"{NODE_URL}/health")
+ if data:
+ version = data.get('version', 'unknown')
+ node_up.labels(version=version).set(1)
+ uptime = data.get('uptime_seconds', 0)
+ node_uptime.set(uptime)
+ logger.info(f"Node health: version={version}, uptime={uptime}s")
+ else:
+ node_up.labels(version='unknown').set(0)
+ logger.warning("Node health check failed")
+
+
+def scrape_epoch():
+ """Scrape /epoch endpoint."""
+ data = fetch_json(f"{NODE_URL}/epoch")
+ if data:
+ current_epoch.set(data.get('epoch', 0))
+ current_slot.set(data.get('slot', 0))
+ epoch_slot_progress.set(data.get('slot_progress', 0.0))
+ epoch_seconds_remaining.set(data.get('seconds_remaining', 0))
+
+ # Update enrolled miners count
+ enrolled = data.get('enrolled_miners', [])
+ enrolled_miners.set(len(enrolled))
+
+ logger.info(f"Epoch: {data.get('epoch')}, Slot: {data.get('slot')}")
+ else:
+ logger.warning("Epoch scrape failed")
+
+
+def scrape_miners():
+ """Scrape /api/miners endpoint."""
+ data = fetch_json(f"{NODE_URL}/api/miners")
+ if data:
+ miners = data.get('miners', [])
+ active_miners.set(len(miners))
+
+ # Update per-miner attestation timestamps
+ for miner in miners:
+ miner_id = miner.get('miner_id', 'unknown')
+ arch = miner.get('architecture', 'unknown')
+ last_attest = miner.get('last_attestation_timestamp', 0)
+ miner_last_attest.labels(miner=miner_id, arch=arch).set(last_attest)
+
+ logger.info(f"Active miners: {len(miners)}")
+ else:
+ logger.warning("Miners scrape failed")
+
+
+def scrape_stats():
+ """Scrape /api/stats endpoint for balance info."""
+ data = fetch_json(f"{NODE_URL}/api/stats")
+ if data:
+ # Update top miner balances
+ top_miners = data.get('top_miners', [])
+ for miner in top_miners:
+ miner_id = miner.get('miner_id', 'unknown')
+ balance = miner.get('balance', 0)
+ balance_rtc.labels(miner=miner_id).set(balance)
+
+ logger.info(f"Updated balances for {len(top_miners)} miners")
+ else:
+ logger.warning("Stats scrape failed")
+
+
+def scrape_hall_of_fame():
+ """Scrape /api/hall_of_fame endpoint."""
+ data = fetch_json(f"{NODE_URL}/api/hall_of_fame")
+ if data:
+ total_machines.set(data.get('total_machines', 0))
+ total_attestations.set(data.get('total_attestations', 0))
+ oldest_machine_year.set(data.get('oldest_machine_year', 0))
+ highest_rust_score.set(data.get('highest_rust_score', 0))
+
+ logger.info(f"Hall of Fame: {data.get('total_machines')} machines, {data.get('total_attestations')} attestations")
+ else:
+ logger.warning("Hall of Fame scrape failed")
+
+
+def scrape_fee_pool():
+ """Scrape /api/fee_pool endpoint."""
+ data = fetch_json(f"{NODE_URL}/api/fee_pool")
+ if data:
+ total_fees_collected.set(data.get('total_fees_rtc', 0))
+ fee_events_total.inc(data.get('fee_events', 0))
+
+ logger.info(f"Fee pool: {data.get('total_fees_rtc')} RTC collected")
+ else:
+ logger.warning("Fee pool scrape failed")
+
+
+def scrape_all():
+ """Scrape all endpoints and update metrics."""
+ logger.info("Starting metrics scrape...")
+
+ scrape_health()
+ scrape_epoch()
+ scrape_miners()
+ scrape_stats()
+ scrape_hall_of_fame()
+ scrape_fee_pool()
+
+ logger.info("Metrics scrape complete")
+
+
+def main():
+ """Main entry point."""
+ logger.info(f"Starting RustChain Prometheus Exporter")
+ logger.info(f"Node URL: {NODE_URL}")
+ logger.info(f"Exporter port: {EXPORTER_PORT}")
+ logger.info(f"Scrape interval: {SCRAPE_INTERVAL}s")
+
+ # Start Prometheus HTTP server
+ start_http_server(EXPORTER_PORT, registry=registry)
+ logger.info(f"Metrics available at http://0.0.0.0:{EXPORTER_PORT}/metrics")
+
+ # Initial scrape
+ scrape_all()
+
+ # Continuous scraping
+ while True:
+ time.sleep(SCRAPE_INTERVAL)
+ scrape_all()
+
+
+if __name__ == '__main__':
+ main()
From ccc8a0a1944b1772f17f9e9665b9179a086ac43a Mon Sep 17 00:00:00 2001
From: Joshualover <43139686+Joshualover@users.noreply.github.com>
Date: Sun, 1 Mar 2026 13:39:29 +0800
Subject: [PATCH 02/25] feat: Add requirements.txt
---
tools/prometheus/requirements.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 tools/prometheus/requirements.txt
diff --git a/tools/prometheus/requirements.txt b/tools/prometheus/requirements.txt
new file mode 100644
index 00000000..956a9868
--- /dev/null
+++ b/tools/prometheus/requirements.txt
@@ -0,0 +1,2 @@
+prometheus_client>=0.17.0
+requests>=2.31.0
From 080cc3ae01cd1c311aef176b15943dd5e9f31ad8 Mon Sep 17 00:00:00 2001
From: Joshualover <43139686+Joshualover@users.noreply.github.com>
Date: Sun, 1 Mar 2026 13:39:30 +0800
Subject: [PATCH 03/25] feat: Add rustchain-exporter.service
---
tools/prometheus/rustchain-exporter.service | 36 +++++++++++++++++++++
1 file changed, 36 insertions(+)
create mode 100644 tools/prometheus/rustchain-exporter.service
diff --git a/tools/prometheus/rustchain-exporter.service b/tools/prometheus/rustchain-exporter.service
new file mode 100644
index 00000000..68579a56
--- /dev/null
+++ b/tools/prometheus/rustchain-exporter.service
@@ -0,0 +1,36 @@
+[Unit]
+Description=RustChain Prometheus Metrics Exporter
+Documentation=https://github.com/Scottcjn/Rustchain
+After=network.target
+
+[Service]
+Type=simple
+User=rustchain
+Group=rustchain
+
+# Environment configuration
+Environment=RUSTCHAIN_NODE_URL=https://rustchain.org
+Environment=EXPORTER_PORT=9100
+Environment=SCRAPE_INTERVAL=60
+
+# Working directory and executable
+WorkingDirectory=/opt/rustchain/tools/prometheus
+ExecStart=/usr/bin/python3 /opt/rustchain/tools/prometheus/rustchain_exporter.py
+
+# Restart policy
+Restart=always
+RestartSec=10
+
+# Security hardening
+NoNewPrivileges=true
+ProtectSystem=strict
+ProtectHome=true
+ReadWritePaths=/var/log/rustchain
+
+# Logging
+StandardOutput=journal
+StandardError=journal
+SyslogIdentifier=rustchain-exporter
+
+[Install]
+WantedBy=multi-user.target
From 58dc1d970bed4760419974b918d11f9f10ee5f10 Mon Sep 17 00:00:00 2001
From: Joshualover <43139686+Joshualover@users.noreply.github.com>
Date: Sun, 1 Mar 2026 13:39:32 +0800
Subject: [PATCH 04/25] feat: Add grafana_dashboard.json
---
tools/prometheus/grafana_dashboard.json | 543 ++++++++++++++++++++++++
1 file changed, 543 insertions(+)
create mode 100644 tools/prometheus/grafana_dashboard.json
diff --git a/tools/prometheus/grafana_dashboard.json b/tools/prometheus/grafana_dashboard.json
new file mode 100644
index 00000000..9ede40ce
--- /dev/null
+++ b/tools/prometheus/grafana_dashboard.json
@@ -0,0 +1,543 @@
+{
+ "annotations": {
+ "list": []
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "id": null,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [
+ {
+ "options": {
+ "0": {
+ "color": "red",
+ "index": 1,
+ "text": "DOWN"
+ },
+ "1": {
+ "color": "green",
+ "index": 0,
+ "text": "UP"
+ }
+ },
+ "type": "value"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "red",
+ "value": null
+ },
+ {
+ "color": "green",
+ "value": 1
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 6,
+ "x": 0,
+ "y": 0
+ },
+ "id": 1,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "expr": "rustchain_node_up",
+ "legendFormat": "Node Status",
+ "refId": "A"
+ }
+ ],
+ "title": "Node Health",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 6,
+ "x": 6,
+ "y": 0
+ },
+ "id": 2,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ }
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "expr": "rustchain_node_uptime_seconds",
+ "legendFormat": "Uptime",
+ "refId": "A"
+ }
+ ],
+ "title": "Node Uptime",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 6,
+ "x": 12,
+ "y": 0
+ },
+ "id": 3,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ }
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "expr": "rustchain_active_miners_total",
+ "legendFormat": "Active Miners",
+ "refId": "A"
+ }
+ ],
+ "title": "Active Miners",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 6,
+ "x": 18,
+ "y": 0
+ },
+ "id": 4,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "none",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ }
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "expr": "rustchain_current_epoch",
+ "legendFormat": "Epoch",
+ "refId": "A"
+ }
+ ],
+ "title": "Current Epoch",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "unit": "percentunit"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 4
+ },
+ "id": 5,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ }
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "expr": "rustchain_epoch_slot_progress",
+ "legendFormat": "Epoch Progress",
+ "refId": "A"
+ }
+ ],
+ "title": "Epoch Slot Progress",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 4
+ },
+ "id": 6,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ }
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "expr": "rustchain_epoch_seconds_remaining",
+ "legendFormat": "Seconds Remaining",
+ "refId": "A"
+ }
+ ],
+ "title": "Epoch Time Remaining",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 12
+ },
+ "id": 7,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "timeSeries",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ }
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "expr": "rustchain_active_miners_total",
+ "legendFormat": "Active Miners",
+ "refId": "A"
+ },
+ {
+ "expr": "rustchain_enrolled_miners_total",
+ "legendFormat": "Enrolled Miners",
+ "refId": "B"
+ }
+ ],
+ "title": "Miner Count Over Time",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 12
+ },
+ "id": 8,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "timeSeries",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ }
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "expr": "rustchain_total_machines",
+ "legendFormat": "Total Machines",
+ "refId": "A"
+ },
+ {
+ "expr": "rustchain_total_attestations",
+ "legendFormat": "Total Attestations",
+ "refId": "B"
+ }
+ ],
+ "title": "Hall of Fame Statistics",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 20
+ },
+ "id": 9,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "timeSeries",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ }
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "expr": "rustchain_balance_rtc",
+ "legendFormat": "{{miner}}",
+ "refId": "A"
+ }
+ ],
+ "title": "Top Miner Balances (RTC)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${DS_PROMETHEUS}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 20
+ },
+ "id": 10,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "timeSeries",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ }
+ },
+ "pluginVersion": "10.0.0",
+ "targets": [
+ {
+ "expr": "rustchain_total_fees_collected_rtc",
+ "legendFormat": "Total Fees (RTC)",
+ "refId": "A"
+ }
+ ],
+ "title": "Fee Pool (RIP-301)",
+ "type": "timeseries"
+ }
+ ],
+ "refresh": "30s",
+ "schemaVersion": 38,
+ "style": "dark",
+ "tags": [
+ "rustchain",
+ "blockchain",
+ "crypto"
+ ],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "selected": false,
+ "text": "Prometheus",
+ "value": "Prometheus"
+ },
+ "hide": 0,
+ "includeAll": false,
+ "label": "Prometheus",
+ "multi": false,
+ "name": "DS_PROMETHEUS",
+ "options": [],
+ "query": "prometheus",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "type": "datasource"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-6h",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "RustChain Node Monitor",
+ "uid": "rustchain-node-monitor",
+ "version": 1,
+ "weekStart": ""
+}
From 846af63d639a8da53b87484fa2262d09bbce00f1 Mon Sep 17 00:00:00 2001
From: Joshualover <43139686+Joshualover@users.noreply.github.com>
Date: Sun, 1 Mar 2026 13:39:33 +0800
Subject: [PATCH 05/25] feat: Add README.md
---
tools/prometheus/README.md | 151 +++++++++++++++++++++++++++++++++++++
1 file changed, 151 insertions(+)
create mode 100644 tools/prometheus/README.md
diff --git a/tools/prometheus/README.md b/tools/prometheus/README.md
new file mode 100644
index 00000000..5fd9e50e
--- /dev/null
+++ b/tools/prometheus/README.md
@@ -0,0 +1,151 @@
+# RustChain Prometheus Metrics Exporter
+
+Prometheus-compatible metrics exporter for RustChain node monitoring.
+
+## Features
+
+- Scrapes RustChain node API every 60 seconds (configurable)
+- Exposes metrics on `:9100/metrics` (configurable port)
+- Pre-built Grafana dashboard with comprehensive panels
+- Systemd service file for production deployment
+
+## Metrics Exported
+
+### Node Health
+- `rustchain_node_up{version}` - Node health status (1=up, 0=down)
+- `rustchain_node_uptime_seconds` - Node uptime in seconds
+
+### Miners
+- `rustchain_active_miners_total` - Total number of active miners
+- `rustchain_enrolled_miners_total` - Total number of enrolled miners
+- `rustchain_miner_last_attest_timestamp{miner,arch}` - Last attestation timestamp per miner
+
+### Epoch
+- `rustchain_current_epoch` - Current epoch number
+- `rustchain_current_slot` - Current slot number
+- `rustchain_epoch_slot_progress` - Epoch slot progress (0.0-1.0)
+- `rustchain_epoch_seconds_remaining` - Seconds remaining in current epoch
+
+### Balances
+- `rustchain_balance_rtc{miner}` - Miner balance in RTC (top miners)
+
+### Hall of Fame
+- `rustchain_total_machines` - Total machines in Hall of Fame
+- `rustchain_total_attestations` - Total attestations
+- `rustchain_oldest_machine_year` - Oldest machine year
+- `rustchain_highest_rust_score` - Highest Rust score
+
+### Fees (RIP-301)
+- `rustchain_total_fees_collected_rtc` - Total fees collected in RTC
+- `rustchain_fee_events_total` - Total fee events
+
+## Installation
+
+### 1. Install Dependencies
+
+```bash
+pip3 install -r requirements.txt
+```
+
+### 2. Configure Environment
+
+Set environment variables (or edit the systemd service file):
+
+```bash
+export RUSTCHAIN_NODE_URL=https://rustchain.org
+export EXPORTER_PORT=9100
+export SCRAPE_INTERVAL=60
+```
+
+### 3. Run Manually
+
+```bash
+python3 rustchain_exporter.py
+```
+
+### 4. Install as Systemd Service (Production)
+
+```bash
+# Copy service file
+sudo cp rustchain-exporter.service /etc/systemd/system/
+
+# Create directories
+sudo mkdir -p /opt/rustchain/tools/prometheus
+sudo mkdir -p /var/log/rustchain
+
+# Copy exporter
+sudo cp rustchain_exporter.py /opt/rustchain/tools/prometheus/
+sudo cp requirements.txt /opt/rustchain/tools/prometheus/
+
+# Install dependencies
+sudo pip3 install -r /opt/rustchain/tools/prometheus/requirements.txt
+
+# Set permissions
+sudo chown -R rustchain:rustchain /opt/rustchain/tools/prometheus
+sudo chown -R rustchain:rustchain /var/log/rustchain
+
+# Enable and start service
+sudo systemctl daemon-reload
+sudo systemctl enable rustchain-exporter
+sudo systemctl start rustchain-exporter
+
+# Check status
+sudo systemctl status rustchain-exporter
+```
+
+## Grafana Dashboard
+
+Import the provided `grafana_dashboard.json` into Grafana:
+
+1. Open Grafana → Dashboards → Import
+2. Upload `grafana_dashboard.json`
+3. Select your Prometheus data source
+4. Click Import
+
+## Verification
+
+Test that metrics are being exposed:
+
+```bash
+curl http://localhost:9100/metrics
+```
+
+Expected output:
+```
+# HELP rustchain_node_up Node health status (1=up, 0=down)
+# TYPE rustchain_node_up gauge
+rustchain_node_up{version="2.2.1-rip200"} 1.0
+# HELP rustchain_node_uptime_seconds Node uptime in seconds
+# TYPE rustchain_node_uptime_seconds gauge
+rustchain_node_uptime_seconds 12345.0
+...
+```
+
+## Prometheus Configuration
+
+Add to `prometheus.yml`:
+
+```yaml
+scrape_configs:
+ - job_name: 'rustchain'
+ static_configs:
+ - targets: ['localhost:9100']
+ scrape_interval: 60s
+```
+
+## Troubleshooting
+
+### Check logs
+```bash
+journalctl -u rustchain-exporter -f
+```
+
+### Test connectivity to node
+```bash
+curl -sk https://rustchain.org/health
+```
+
+### Verify Python dependencies
+```bash
+pip3 list | grep prometheus_client
+```
From 9b18bae2ab0675784b84fff41f9e96017a67476b Mon Sep 17 00:00:00 2001
From: Joshualover <43139686+Joshualover@users.noreply.github.com>
Date: Sun, 1 Mar 2026 13:39:35 +0800
Subject: [PATCH 06/25] docs: Add swagger.html
---
docs/api/swagger.html | 76 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
create mode 100644 docs/api/swagger.html
diff --git a/docs/api/swagger.html b/docs/api/swagger.html
new file mode 100644
index 00000000..8369ba29
--- /dev/null
+++ b/docs/api/swagger.html
@@ -0,0 +1,76 @@
+
+
+
+
+
+ RustChain Node API - Swagger UI
+
+
+
+
+
+
+
+
+
+
+
From 4c900c3a5ecbefe00d371fecd8d6f603727f40d7 Mon Sep 17 00:00:00 2001
From: Joshualover <43139686+Joshualover@users.noreply.github.com>
Date: Sun, 1 Mar 2026 13:39:36 +0800
Subject: [PATCH 07/25] docs: Add README.md
---
docs/api/README.md | 161 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 161 insertions(+)
create mode 100644 docs/api/README.md
diff --git a/docs/api/README.md b/docs/api/README.md
new file mode 100644
index 00000000..0b5072e2
--- /dev/null
+++ b/docs/api/README.md
@@ -0,0 +1,161 @@
+# RustChain Node API Documentation
+
+OpenAPI 3.0 specification and Swagger UI for the RustChain node API.
+
+## Files
+
+- `openapi.yaml` - OpenAPI 3.0 specification
+- `swagger.html` - Self-contained Swagger UI page
+
+## Endpoints Documented
+
+### Public Endpoints (No Authentication)
+
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| GET | `/health` | Node health check |
+| GET | `/ready` | Readiness probe |
+| GET | `/epoch` | Current epoch, slot, enrolled miners |
+| GET | `/api/miners` | Active miners with attestation data |
+| GET | `/api/stats` | Network statistics |
+| GET | `/api/hall_of_fame` | Hall of Fame leaderboard (5 categories) |
+| GET | `/api/fee_pool` | RIP-301 fee pool statistics |
+| GET | `/balance?miner_id=X` | Miner balance lookup |
+| GET | `/lottery/eligibility?miner_id=X` | Epoch eligibility check |
+| GET | `/explorer` | Block explorer page |
+
+### Authenticated Endpoints (X-Admin-Key Header)
+
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| POST | `/attest/submit` | Submit hardware attestation |
+| POST | `/wallet/transfer/signed` | Ed25519 signed transfer |
+| POST | `/wallet/transfer` | Admin transfer (requires admin key) |
+| POST | `/withdraw/request` | Withdrawal request |
+
+## Usage
+
+### View Documentation Locally
+
+1. Open `swagger.html` in a web browser
+2. The page will load the OpenAPI spec from `openapi.yaml`
+3. Use "Try it out" to test endpoints against the live node
+
+### Host with Python
+
+```bash
+# Serve files locally
+python3 -m http.server 8080
+
+# Open in browser
+open http://localhost:8080/swagger.html
+```
+
+### Validate Spec
+
+```bash
+# Install swagger-cli
+npm install -g swagger-cli
+
+# Validate
+swagger-cli validate openapi.yaml
+```
+
+### Test Against Live Node
+
+Test endpoints against the production node:
+
+```bash
+# Health check
+curl -sk https://rustchain.org/health | jq
+
+# Epoch info
+curl -sk https://rustchain.org/epoch | jq
+
+# Active miners
+curl -sk https://rustchain.org/api/miners | jq
+
+# Hall of Fame
+curl -sk https://rustchain.org/api/hall_of_fame | jq
+```
+
+## Integration
+
+### Import into Postman
+
+1. Open Postman
+2. File → Import
+3. Select `openapi.yaml`
+4. Collection created with all endpoints
+
+### Generate Client SDKs
+
+```bash
+# Python client
+openapi-generator generate -i openapi.yaml -g python -o ./client-python
+
+# JavaScript client
+openapi-generator generate -i openapi.yaml -g javascript -o ./client-js
+
+# Go client
+openapi-generator generate -i openapi.yaml -g go -o ./client-go
+```
+
+### Embed in Documentation
+
+The `swagger.html` file is self-contained and can be:
+- Hosted on any static web server
+- Embedded in existing documentation sites
+- Served directly from the RustChain node
+
+## API Response Examples
+
+### Health Check
+```json
+{
+ "status": "ok",
+ "version": "2.2.1-rip200",
+ "uptime_seconds": 12345,
+ "timestamp": 1740783600
+}
+```
+
+### Epoch Info
+```json
+{
+ "epoch": 88,
+ "slot": 12700,
+ "slot_progress": 0.45,
+ "seconds_remaining": 300,
+ "enrolled_miners": [
+ {
+ "miner_id": "dual-g4-125",
+ "architecture": "G4",
+ "rust_score": 450.5
+ }
+ ]
+}
+```
+
+### Miner List
+```json
+{
+ "miners": [
+ {
+ "miner_id": "dual-g4-125",
+ "architecture": "G4",
+ "rust_score": 450.5,
+ "last_attestation_timestamp": 1740783600,
+ "attestations_count": 150,
+ "status": "active"
+ }
+ ]
+}
+```
+
+## Version History
+
+- **2.2.1-rip200** - Current version with RIP-200 and RIP-301 support
+- Added fee pool endpoints
+- Added Hall of Fame categories
+- Enhanced attestation response format
From 4db1d80f123cae64f1c4ffc0916e693cd01c5c50 Mon Sep 17 00:00:00 2001
From: jeanmiliuiu-boop
Date: Sun, 1 Mar 2026 00:17:42 +0800
Subject: [PATCH 08/25] fix: standardize Explorer URL to HTTPS
* Fix: Node URL defaults inconsistent across files
- Unify Node URL to https://50.28.86.131
- Fix wallet and miners default URLs
Fixes #400
## Bounty Payment
**Wallet (Base):** 0xd7C80bdf514dd0029e20e442E227872A63a91A2D
**Token:** RTC
* fix: standardize node URL to HTTPS in INSTALL.md explorer link
---------
Co-authored-by: JeanmiLiu <>
---
INSTALL.md | 2 +-
miners/ppc/g4/rustchain_g4_poa_miner_v2.py | 2 +-
.../ppc/g4/rustchain_g4_poa_miner_v2.py.tmp | 457 ++++++++++++++++++
miners/ppc/g5/g5_miner.sh | 2 +-
miners/ppc/g5/g5_miner.sh.tmp | 49 ++
.../ppc/rustchain_powerpc_g4_miner_v2.2.2.py | 2 +-
.../rustchain_powerpc_g4_miner_v2.2.2.py.tmp | 352 ++++++++++++++
wallet/rustchain_wallet_ppc.py | 2 +-
wallet/rustchain_wallet_ppc.py.tmp | 316 ++++++++++++
9 files changed, 1179 insertions(+), 5 deletions(-)
create mode 100644 miners/ppc/g4/rustchain_g4_poa_miner_v2.py.tmp
create mode 100755 miners/ppc/g5/g5_miner.sh.tmp
create mode 100644 miners/ppc/rustchain_powerpc_g4_miner_v2.2.2.py.tmp
create mode 100644 wallet/rustchain_wallet_ppc.py.tmp
diff --git a/INSTALL.md b/INSTALL.md
index f67e15b1..b347ea27 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -338,7 +338,7 @@ curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-mine
- **Documentation:** https://github.com/Scottcjn/Rustchain
- **Issues:** https://github.com/Scottcjn/Rustchain/issues
-- **Explorer:** http://50.28.86.131/explorer
+- **Explorer:** https://50.28.86.131/explorer
- **Bounties:** https://github.com/Scottcjn/rustchain-bounties
## Security Notes
diff --git a/miners/ppc/g4/rustchain_g4_poa_miner_v2.py b/miners/ppc/g4/rustchain_g4_poa_miner_v2.py
index a6adebb1..e6cbb431 100644
--- a/miners/ppc/g4/rustchain_g4_poa_miner_v2.py
+++ b/miners/ppc/g4/rustchain_g4_poa_miner_v2.py
@@ -15,7 +15,7 @@
from datetime import datetime
# Configuration
-NODE_URL = os.environ.get("RUSTCHAIN_NODE", "http://50.28.86.131:8088")
+NODE_URL = os.environ.get("RUSTCHAIN_NODE", "https://50.28.86.131")
ATTESTATION_TTL = 600 # 10 minutes - must re-attest before this
LOTTERY_CHECK_INTERVAL = 10 # Check every 10 seconds
ATTESTATION_INTERVAL = 300 # Re-attest every 5 minutes
diff --git a/miners/ppc/g4/rustchain_g4_poa_miner_v2.py.tmp b/miners/ppc/g4/rustchain_g4_poa_miner_v2.py.tmp
new file mode 100644
index 00000000..a6adebb1
--- /dev/null
+++ b/miners/ppc/g4/rustchain_g4_poa_miner_v2.py.tmp
@@ -0,0 +1,457 @@
+#!/usr/bin/env python3
+"""
+RustChain G4 PoA Miner v2.0
+Fixed: Uses miner_id consistently for attestation and lottery
+Implements full Proof of Antiquity signals per rip_proof_of_antiquity_hardware.py
+"""
+import os
+import sys
+import time
+import json
+import hashlib
+import platform
+import subprocess
+import requests
+from datetime import datetime
+
+# Configuration
+NODE_URL = os.environ.get("RUSTCHAIN_NODE", "http://50.28.86.131:8088")
+ATTESTATION_TTL = 600 # 10 minutes - must re-attest before this
+LOTTERY_CHECK_INTERVAL = 10 # Check every 10 seconds
+ATTESTATION_INTERVAL = 300 # Re-attest every 5 minutes
+
+# G4 CPU timing profile from PoA spec
+# ~8500 µs per 10k SHA256 operations
+G4_TIMING_MEAN = 8500
+G4_TIMING_VARIANCE_MIN = 200
+G4_TIMING_VARIANCE_MAX = 800
+
+
+def get_system_entropy(size=64):
+ """Collect real entropy from system"""
+ try:
+ return os.urandom(size).hex()
+ except Exception:
+ # Fallback: use timing jitter
+ samples = []
+ for _ in range(size):
+ start = time.perf_counter_ns()
+ hashlib.sha256(str(time.time_ns()).encode()).digest()
+ samples.append(time.perf_counter_ns() - start)
+ return hashlib.sha256(bytes(samples[:64])).hexdigest() * 2
+
+
+def measure_cpu_timing(iterations=10):
+ """
+ Measure actual CPU timing for SHA256 operations
+ Returns timing samples in microseconds
+ """
+ samples = []
+ for _ in range(iterations):
+ start = time.perf_counter()
+ # Do 10k SHA256 operations
+ data = b"rustchain_poa_benchmark"
+ for _ in range(10000):
+ data = hashlib.sha256(data).digest()
+ elapsed_us = (time.perf_counter() - start) * 1_000_000
+ samples.append(int(elapsed_us))
+ return samples
+
+
+def measure_ram_timing():
+ """
+ Measure RAM access patterns for PoA validation
+ Returns timing in nanoseconds
+ """
+ # Sequential memory access
+ test_data = bytearray(1024 * 1024) # 1MB
+ start = time.perf_counter_ns()
+ for i in range(0, len(test_data), 64):
+ test_data[i] = (test_data[i] + 1) % 256
+ sequential_ns = (time.perf_counter_ns() - start) / (len(test_data) // 64)
+
+ # Random access pattern
+ import random
+ indices = [random.randint(0, len(test_data)-1) for _ in range(1000)]
+ start = time.perf_counter_ns()
+ for idx in indices:
+ test_data[idx] = (test_data[idx] + 1) % 256
+ random_ns = (time.perf_counter_ns() - start) / len(indices)
+
+ # Estimate cache hit rate (lower random/sequential ratio = better cache)
+ cache_hit_rate = min(1.0, sequential_ns / max(random_ns, 1) * 2)
+
+ return {
+ "sequential_ns": int(sequential_ns),
+ "random_ns": int(random_ns),
+ "cache_hit_rate": round(cache_hit_rate, 2)
+ }
+
+
+def get_mac_addresses():
+ """Get MAC addresses for hardware fingerprinting"""
+ macs = []
+ try:
+ if platform.system() == "Darwin":
+ result = subprocess.run(["ifconfig"], capture_output=True, text=True)
+ for line in result.stdout.split('\n'):
+ if 'ether' in line:
+ mac = line.split('ether')[1].strip().split()[0]
+ if mac and mac != "00:00:00:00:00:00":
+ macs.append(mac)
+ elif platform.system() == "Linux":
+ result = subprocess.run(["ip", "link"], capture_output=True, text=True)
+ for line in result.stdout.split('\n'):
+ if 'link/ether' in line:
+ mac = line.split('link/ether')[1].strip().split()[0]
+ if mac and mac != "00:00:00:00:00:00":
+ macs.append(mac)
+ except Exception:
+ pass
+ return macs[:3] if macs else ["00:03:93:00:00:01"] # Apple OUI fallback
+
+
+def detect_ppc_hardware():
+ """Detect PowerPC hardware details"""
+ hw_info = {
+ "family": "PowerPC",
+ "arch": "G4",
+ "model": "PowerMac G4",
+ "cpu": "PowerPC G4 7450",
+ "cores": 1,
+ "memory_gb": 1
+ }
+
+ try:
+ machine = platform.machine().lower()
+ if 'ppc' in machine or 'power' in machine:
+ hw_info["family"] = "PowerPC"
+
+ # Try to detect specific model
+ if platform.system() == "Darwin":
+ result = subprocess.run(['system_profiler', 'SPHardwareDataType'],
+ capture_output=True, text=True, timeout=10)
+ output = result.stdout.lower()
+
+ if 'g5' in output or 'powermac11' in output:
+ hw_info["arch"] = "G5"
+ hw_info["cpu"] = "PowerPC G5"
+ elif 'g4' in output or 'powermac3' in output or 'powerbook' in output:
+ hw_info["arch"] = "G4"
+ hw_info["cpu"] = "PowerPC G4"
+ elif 'g3' in output:
+ hw_info["arch"] = "G3"
+ hw_info["cpu"] = "PowerPC G3"
+
+ elif platform.system() == "Linux":
+ with open('/proc/cpuinfo', 'r') as f:
+ cpuinfo = f.read().lower()
+ if '7450' in cpuinfo or '7447' in cpuinfo or '7455' in cpuinfo:
+ hw_info["arch"] = "G4"
+ hw_info["cpu"] = "PowerPC G4 (74xx)"
+ elif '970' in cpuinfo:
+ hw_info["arch"] = "G5"
+ hw_info["cpu"] = "PowerPC G5 (970)"
+ elif '750' in cpuinfo:
+ hw_info["arch"] = "G3"
+ hw_info["cpu"] = "PowerPC G3 (750)"
+ except Exception:
+ pass
+
+ # Get core count
+ hw_info["cores"] = os.cpu_count() or 1
+
+ # Get memory
+ try:
+ if platform.system() == "Linux":
+ with open('/proc/meminfo', 'r') as f:
+ for line in f:
+ if 'MemTotal' in line:
+ kb = int(line.split()[1])
+ hw_info["memory_gb"] = max(1, kb // (1024 * 1024))
+ break
+ elif platform.system() == "Darwin":
+ result = subprocess.run(['sysctl', '-n', 'hw.memsize'],
+ capture_output=True, text=True, timeout=5)
+ hw_info["memory_gb"] = int(result.stdout.strip()) // (1024**3)
+ except Exception:
+ pass
+
+ return hw_info
+
+
+class G4PoAMiner:
+ def __init__(self, miner_id=None):
+ self.node_url = NODE_URL
+ self.hw_info = detect_ppc_hardware()
+
+ # Generate or use provided miner_id
+ if miner_id:
+ self.miner_id = miner_id
+ else:
+ hostname = platform.node()[:10]
+ hw_hash = hashlib.sha256(f"{hostname}-{self.hw_info['cpu']}".encode()).hexdigest()[:8]
+ self.miner_id = f"g4-{hostname}-{hw_hash}"
+
+ self.attestation_valid_until = 0
+ self.shares_submitted = 0
+ self.shares_accepted = 0
+ self.current_slot = 0
+
+ self._print_banner()
+
+ def _print_banner(self):
+ print("=" * 70)
+ print("RustChain G4 PoA Miner v2.0")
+ print("=" * 70)
+ print(f"Miner ID: {self.miner_id}")
+ print(f"Node: {self.node_url}")
+ print("-" * 70)
+ print(f"Hardware: {self.hw_info['family']} / {self.hw_info['arch']}")
+ print(f"CPU: {self.hw_info['cpu']}")
+ print(f"Cores: {self.hw_info['cores']}")
+ print(f"Memory: {self.hw_info['memory_gb']} GB")
+ print("-" * 70)
+ print("Expected PoA Weight: 2.5x (G4 Antiquity Bonus)")
+ print("=" * 70)
+
+ def attest(self):
+ """
+ Complete hardware attestation with full PoA signals
+ Per rip_proof_of_antiquity_hardware.py:
+ - entropy_samples (40% weight)
+ - cpu_timing (30% weight)
+ - ram_timing (20% weight)
+ - macs (10% weight)
+ """
+ print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Attesting with PoA signals...")
+
+ try:
+ # Step 1: Get challenge nonce
+ resp = requests.post(f"{self.node_url}/attest/challenge", json={}, timeout=15)
+ if resp.status_code != 200:
+ print(f" ERROR: Challenge failed ({resp.status_code})")
+ return False
+
+ challenge = resp.json()
+ nonce = challenge.get("nonce", "")
+ print(f" Got nonce: {nonce[:16]}...")
+
+ # Step 2: Collect PoA signals
+ # Entropy (40% weight)
+ entropy_hex = get_system_entropy(64)
+ print(f" Entropy: {entropy_hex[:32]}... ({len(entropy_hex)//2} bytes)")
+
+ # CPU Timing (30% weight) - measure actual timing
+ print(" Measuring CPU timing...")
+ cpu_samples = measure_cpu_timing(10)
+ cpu_mean = sum(cpu_samples) / len(cpu_samples)
+ cpu_variance = sum((x - cpu_mean)**2 for x in cpu_samples) / len(cpu_samples)
+ print(f" CPU timing: mean={cpu_mean:.0f}µs, var={cpu_variance:.0f}")
+
+ # RAM Timing (20% weight)
+ print(" Measuring RAM timing...")
+ ram_timing = measure_ram_timing()
+ print(f" RAM timing: seq={ram_timing['sequential_ns']}ns, rand={ram_timing['random_ns']}ns")
+
+ # MACs (10% weight)
+ macs = get_mac_addresses()
+ print(f" MACs: {macs}")
+
+ # Step 3: Build commitment
+ commitment = hashlib.sha256(f"{nonce}{self.miner_id}{entropy_hex}".encode()).hexdigest()
+
+ # Step 4: Build attestation payload
+ # KEY FIX: Use miner_id as the miner field for consistent identity
+ attestation = {
+ "miner": self.miner_id, # IMPORTANT: Use miner_id here for lottery compatibility
+ "miner_id": self.miner_id,
+ "nonce": nonce,
+ "report": {
+ "nonce": nonce,
+ "commitment": commitment
+ },
+ "device": {
+ "family": self.hw_info["family"],
+ "arch": self.hw_info["arch"],
+ "model": self.hw_info["model"],
+ "cpu": self.hw_info["cpu"],
+ "cores": self.hw_info["cores"],
+ "memory_gb": self.hw_info["memory_gb"]
+ },
+ "signals": {
+ "entropy_samples": entropy_hex,
+ "cpu_timing": {
+ "samples": cpu_samples,
+ "mean": cpu_mean,
+ "variance": cpu_variance
+ },
+ "ram_timing": ram_timing,
+ "macs": macs,
+ "hostname": platform.node(),
+ "os": platform.system().lower(),
+ "timestamp": int(time.time())
+ }
+ }
+
+ # Step 5: Submit attestation
+ print(" Submitting attestation...")
+ resp = requests.post(f"{self.node_url}/attest/submit",
+ json=attestation, timeout=15)
+
+ if resp.status_code == 200:
+ result = resp.json()
+ if result.get("ok") or result.get("status") == "accepted":
+ self.attestation_valid_until = time.time() + ATTESTATION_INTERVAL
+ print(f" SUCCESS: Attestation accepted!")
+ print(f" Ticket: {result.get('ticket_id', 'N/A')}")
+ return True
+ else:
+ print(f" WARNING: {result}")
+ return False
+ else:
+ print(f" ERROR: HTTP {resp.status_code}")
+ print(f" Response: {resp.text[:200]}")
+ return False
+
+ except Exception as e:
+ print(f" ERROR: {e}")
+ return False
+
+ def check_eligibility(self):
+ """Check if we're the designated block producer for current slot"""
+ try:
+ resp = requests.get(
+ f"{self.node_url}/lottery/eligibility",
+ params={"miner_id": self.miner_id},
+ timeout=10
+ )
+
+ if resp.status_code == 200:
+ return resp.json()
+ return {"eligible": False, "reason": f"HTTP {resp.status_code}"}
+
+ except Exception as e:
+ return {"eligible": False, "reason": str(e)}
+
+ def submit_header(self, slot):
+ """Submit a signed header for the slot"""
+ try:
+ # Create message
+ ts = int(time.time())
+ message = f"slot:{slot}:miner:{self.miner_id}:ts:{ts}"
+ message_hex = message.encode().hex()
+
+ # Sign with Blake2b (per PoA spec)
+ sig_data = hashlib.blake2b(
+ f"{message}{self.miner_id}".encode(),
+ digest_size=64
+ ).hexdigest()
+
+ header_payload = {
+ "miner_id": self.miner_id,
+ "header": {
+ "slot": slot,
+ "miner": self.miner_id,
+ "timestamp": ts
+ },
+ "message": message_hex,
+ "signature": sig_data,
+ "pubkey": self.miner_id
+ }
+
+ resp = requests.post(
+ f"{self.node_url}/headers/ingest_signed",
+ json=header_payload,
+ timeout=15
+ )
+
+ self.shares_submitted += 1
+
+ if resp.status_code == 200:
+ result = resp.json()
+ if result.get("ok"):
+ self.shares_accepted += 1
+ return True, result
+ return False, result
+ return False, {"error": f"HTTP {resp.status_code}"}
+
+ except Exception as e:
+ return False, {"error": str(e)}
+
+ def run(self):
+ """Main mining loop"""
+ print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Starting miner...")
+
+ # Initial attestation
+ while not self.attest():
+ print(" Retrying attestation in 30 seconds...")
+ time.sleep(30)
+
+ last_slot = 0
+ status_counter = 0
+
+ while True:
+ try:
+ # Re-attest if needed
+ if time.time() > self.attestation_valid_until:
+ self.attest()
+
+ # Check lottery eligibility
+ eligibility = self.check_eligibility()
+ slot = eligibility.get("slot", 0)
+ self.current_slot = slot
+
+ if eligibility.get("eligible"):
+ print(f"\n[{datetime.now().strftime('%H:%M:%S')}] ELIGIBLE for slot {slot}!")
+
+ if slot != last_slot:
+ success, result = self.submit_header(slot)
+ if success:
+ print(f" Header ACCEPTED! Slot {slot}")
+ else:
+ print(f" Header rejected: {result}")
+ last_slot = slot
+ else:
+ reason = eligibility.get("reason", "unknown")
+ if reason == "not_attested":
+ print(f"[{datetime.now().strftime('%H:%M:%S')}] Not attested - re-attesting...")
+ self.attest()
+ elif reason == "not_your_turn":
+ # Normal - wait for our turn
+ pass
+
+ # Status update every 6 checks (~60 seconds)
+ status_counter += 1
+ if status_counter >= 6:
+ rotation = eligibility.get("rotation_size", 0)
+ producer = eligibility.get("slot_producer", "?")
+ print(f"[{datetime.now().strftime('%H:%M:%S')}] "
+ f"Slot {slot} | Producer: {producer[:15] if producer else '?'}... | "
+ f"Rotation: {rotation} | "
+ f"Submitted: {self.shares_submitted} | Accepted: {self.shares_accepted}")
+ status_counter = 0
+
+ time.sleep(LOTTERY_CHECK_INTERVAL)
+
+ except KeyboardInterrupt:
+ print("\n\nShutting down miner...")
+ break
+ except Exception as e:
+ print(f"[{datetime.now().strftime('%H:%M:%S')}] Error: {e}")
+ time.sleep(30)
+
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description="RustChain G4 PoA Miner")
+ parser.add_argument("--miner-id", "-m", help="Custom miner ID")
+ parser.add_argument("--node", "-n", default=NODE_URL, help="RIP node URL")
+ args = parser.parse_args()
+
+ if args.node:
+ NODE_URL = args.node
+
+ miner = G4PoAMiner(miner_id=args.miner_id)
+ miner.run()
diff --git a/miners/ppc/g5/g5_miner.sh b/miners/ppc/g5/g5_miner.sh
index f2485b73..ac89c2f4 100755
--- a/miners/ppc/g5/g5_miner.sh
+++ b/miners/ppc/g5/g5_miner.sh
@@ -3,7 +3,7 @@
# Power Mac G5 Dual 2GHz - 2.0x Antiquity Bonus
WALLET="ppc_g5_130_$(hostname | md5)RTC"
-RIP_URL="http://50.28.86.131:8088"
+RIP_URL="https://50.28.86.131"
echo "=== RustChain G5 Miner ==="
echo "Wallet: $WALLET"
diff --git a/miners/ppc/g5/g5_miner.sh.tmp b/miners/ppc/g5/g5_miner.sh.tmp
new file mode 100755
index 00000000..f2485b73
--- /dev/null
+++ b/miners/ppc/g5/g5_miner.sh.tmp
@@ -0,0 +1,49 @@
+#\!/bin/sh
+# RustChain G5 Miner - Shell Script for Python 2.5 compatibility
+# Power Mac G5 Dual 2GHz - 2.0x Antiquity Bonus
+
+WALLET="ppc_g5_130_$(hostname | md5)RTC"
+RIP_URL="http://50.28.86.131:8088"
+
+echo "=== RustChain G5 Miner ==="
+echo "Wallet: $WALLET"
+echo "Architecture: PowerPC G5 (2.0x bonus)"
+
+while true; do
+ echo ""
+ echo "=== Generating Entropy at $(date) ==="
+
+ # Collect timing samples using time command
+ SAMPLES=""
+ for i in $(seq 1 100); do
+ START=$(perl -e "print time()")
+ x=1
+ for j in $(seq 1 50); do x=$((x + j)); done
+ END=$(perl -e "print time()")
+ SAMPLES="$SAMPLES$((END - START)),"
+ done
+
+ # Generate entropy hash
+ ENTROPY=$(echo "$SAMPLES$(date +%s)" | md5)
+ TIMESTAMP=$(date +%s)000
+
+ echo "Entropy Hash: $ENTROPY"
+ echo "Submitting to RIP service..."
+
+ # Get challenge
+ CHALLENGE=$(curl -s -X POST "$RIP_URL/attest/challenge" -H "Content-Type: application/json" 2>/dev/null)
+ NONCE=$(echo "$CHALLENGE" | sed -n "s/.*nonce.*:\s*\"\([^\"]*\)\".*/\1/p")
+
+ if [ -n "$NONCE" ]; then
+ # Submit attestation
+ RESULT=$(curl -s -X POST "$RIP_URL/attest/submit" \
+ -H "Content-Type: application/json" \
+ -d "{\"miner\":\"$WALLET\",\"report\":{\"nonce\":\"$NONCE\"},\"device\":{\"hostname\":\"$(hostname)\",\"arch\":\"G5\",\"family\":\"PowerPC G5\",\"os\":\"Darwin 9.8.0\"},\"signals\":{\"entropy_hash\":\"$ENTROPY\",\"sample_count\":100}}" 2>/dev/null)
+ echo "Result: $RESULT"
+ else
+ echo "Failed to get challenge"
+ fi
+
+ echo "Sleeping 600 seconds..."
+ sleep 600
+done
diff --git a/miners/ppc/rustchain_powerpc_g4_miner_v2.2.2.py b/miners/ppc/rustchain_powerpc_g4_miner_v2.2.2.py
index e988f836..803faf98 100644
--- a/miners/ppc/rustchain_powerpc_g4_miner_v2.2.2.py
+++ b/miners/ppc/rustchain_powerpc_g4_miner_v2.2.2.py
@@ -6,7 +6,7 @@
import os, sys, json, time, hashlib, uuid, requests, statistics, subprocess, re
from datetime import datetime
-NODE_URL = "http://50.28.86.131:8088"
+NODE_URL = "https://50.28.86.131"
BLOCK_TIME = 600 # 10 minutes
LOTTERY_CHECK_INTERVAL = 10 # Check every 10 seconds
diff --git a/miners/ppc/rustchain_powerpc_g4_miner_v2.2.2.py.tmp b/miners/ppc/rustchain_powerpc_g4_miner_v2.2.2.py.tmp
new file mode 100644
index 00000000..e988f836
--- /dev/null
+++ b/miners/ppc/rustchain_powerpc_g4_miner_v2.2.2.py.tmp
@@ -0,0 +1,352 @@
+#!/usr/bin/env python3
+"""
+RustChain PowerPC G4 Miner - FIXED VERSION WITH HEADER SUBMISSION
+Includes proper lottery checking and header submission flow
+"""
+import os, sys, json, time, hashlib, uuid, requests, statistics, subprocess, re
+from datetime import datetime
+
+NODE_URL = "http://50.28.86.131:8088"
+BLOCK_TIME = 600 # 10 minutes
+LOTTERY_CHECK_INTERVAL = 10 # Check every 10 seconds
+
+class G4Miner:
+ def __init__(self, miner_id="dual-g4-125", wallet=None):
+ self.node_url = NODE_URL
+ self.miner_id = miner_id
+ self.wallet = wallet or f"ppc_g4_{hashlib.sha256(f'{miner_id}-{time.time()}'.encode()).hexdigest()[:38]}RTC"
+ self.enrolled = False
+ self.attestation_valid_until = 0
+ self.shares_submitted = 0
+ self.shares_accepted = 0
+ self.last_entropy = {}
+
+ # PowerPC G4 hardware profile
+ self.hw_info = self._detect_hardware()
+
+ print("="*70)
+ print("RustChain PowerPC G4 Miner - v2.2.2 (Header Submission Fix)")
+ print("="*70)
+ print(f"Miner ID: {self.miner_id}")
+ print(f"Wallet: {self.wallet}")
+ print(f"Hardware: {self.hw_info['cpu']}")
+ print(f"Expected Weight: 2.5x (PowerPC/G4)")
+ print("="*70)
+
+ def attest(self):
+ """Complete hardware attestation"""
+ print(f"\n🔐 [{datetime.now().strftime('%H:%M:%S')}] Attesting as PowerPC G4...")
+
+ try:
+ # Step 1: Get challenge
+ resp = requests.post(f"{self.node_url}/attest/challenge", json={}, timeout=10)
+ if resp.status_code != 200:
+ print(f"❌ Challenge failed: {resp.status_code}")
+ return False
+
+ challenge = resp.json()
+ nonce = challenge.get("nonce")
+ print(f"✅ Got challenge nonce")
+
+ except Exception as e:
+ print(f"❌ Challenge error: {e}")
+ return False
+
+ # Step 2: Submit attestation
+ entropy = self._collect_entropy()
+ self.last_entropy = entropy
+
+ attestation = {
+ "miner": self.wallet,
+ "miner_id": self.miner_id,
+ "nonce": nonce,
+ "report": {
+ "nonce": nonce,
+ "commitment": hashlib.sha256(
+ (nonce + self.wallet + json.dumps(entropy, sort_keys=True)).encode()
+ ).hexdigest(),
+ "derived": entropy,
+ "entropy_score": entropy.get("variance_ns", 0.0)
+ },
+ "device": {
+ "family": self.hw_info["family"],
+ "arch": self.hw_info["arch"],
+ "model": self.hw_info["model"],
+ "cpu": self.hw_info["cpu"],
+ "cores": self.hw_info["cores"],
+ "memory_gb": self.hw_info["memory_gb"]
+ },
+ "signals": {
+ "macs": self.hw_info.get("macs", [self.hw_info["mac"]]),
+ "hostname": self.hw_info["hostname"]
+ }
+ }
+
+ try:
+ resp = requests.post(f"{self.node_url}/attest/submit",
+ json=attestation, timeout=30)
+
+ if resp.status_code == 200:
+ result = resp.json()
+ if result.get("ok"):
+ self.attestation_valid_until = time.time() + 580
+ print(f"✅ Attestation accepted! Valid for 580 seconds")
+ return True
+ else:
+ print(f"❌ Rejected: {result}")
+ else:
+ print(f"❌ HTTP {resp.status_code}: {resp.text[:200]}")
+
+ except Exception as e:
+ print(f"❌ Error: {e}")
+
+ return False
+
+ def enroll(self):
+ """Enroll in current epoch"""
+ # Check attestation validity
+ if time.time() >= self.attestation_valid_until:
+ print(f"📝 Attestation expired, re-attesting...")
+ if not self.attest():
+ return False
+
+ print(f"\n📝 [{datetime.now().strftime('%H:%M:%S')}] Enrolling in epoch...")
+
+ payload = {
+ "miner_pubkey": self.wallet,
+ "miner_id": self.miner_id,
+ "device": {
+ "family": self.hw_info["family"],
+ "arch": self.hw_info["arch"]
+ }
+ }
+
+ try:
+ resp = requests.post(f"{self.node_url}/epoch/enroll",
+ json=payload, timeout=30)
+
+ if resp.status_code == 200:
+ result = resp.json()
+ if result.get("ok"):
+ self.enrolled = True
+ weight = result.get('weight', 1.0)
+ print(f"✅ Enrolled successfully!")
+ print(f" Epoch: {result.get('epoch')}")
+ print(f" Weight: {weight}x {'✅' if weight >= 2.5 else '⚠️'}")
+ return True
+ else:
+ print(f"❌ Failed: {result}")
+ else:
+ error_data = resp.json() if resp.headers.get('content-type') == 'application/json' else {}
+ print(f"❌ HTTP {resp.status_code}: {error_data.get('error', resp.text[:200])}")
+
+ except Exception as e:
+ print(f"❌ Error: {e}")
+
+ return False
+
+ def check_lottery(self):
+ """Check if eligible to submit header"""
+ try:
+ resp = requests.get(
+ f"{self.node_url}/lottery/eligibility",
+ params={"miner_id": self.miner_id},
+ timeout=5
+ )
+
+ if resp.status_code == 200:
+ result = resp.json()
+ return result.get("eligible", False), result
+
+ except Exception as e:
+ # Silently fail - lottery checks happen frequently
+ pass
+
+ return False, {}
+
+ def submit_header(self, slot):
+ """Submit block header when lottery eligible"""
+ # Generate mock signature (testnet mode allows this)
+ message = f"{slot}{self.miner_id}{time.time()}"
+ message_hash = hashlib.sha256(message.encode()).hexdigest()
+
+ # Mock signature for testnet
+ mock_signature = "0" * 128 # Testnet mode accepts this
+
+ header = {
+ "miner_id": self.miner_id,
+ "slot": slot,
+ "message": message_hash,
+ "signature": mock_signature,
+ "pubkey": self.wallet[:64] # Inline pubkey (testnet mode)
+ }
+
+ try:
+ resp = requests.post(
+ f"{self.node_url}/headers/ingest_signed",
+ json=header,
+ timeout=10
+ )
+
+ self.shares_submitted += 1
+
+ if resp.status_code == 200:
+ result = resp.json()
+ if result.get("ok"):
+ self.shares_accepted += 1
+ print(f" ✅ Header accepted! (Slot {slot})")
+ print(f" 📊 Stats: {self.shares_accepted}/{self.shares_submitted} accepted")
+ return True
+ else:
+ print(f" ❌ Header rejected: {result.get('error', 'unknown')}")
+ else:
+ print(f" ❌ HTTP {resp.status_code}: {resp.text[:100]}")
+
+ except Exception as e:
+ print(f" ❌ Submit error: {e}")
+
+ return False
+
+ def check_balance(self):
+ """Check balance"""
+ try:
+ resp = requests.get(f"{self.node_url}/balance/{self.wallet}", timeout=10)
+ if resp.status_code == 200:
+ result = resp.json()
+ balance = result.get('balance_rtc', 0)
+ print(f"\n💰 Balance: {balance} RTC")
+ return balance
+ except:
+ pass
+ return 0
+
+ def mine_forever(self):
+ """Keep mining continuously with lottery checking"""
+ print(f"\n⛏️ Starting continuous mining with lottery checking...")
+ print(f"Checking lottery every {LOTTERY_CHECK_INTERVAL} seconds")
+ print(f"Press Ctrl+C to stop\n")
+
+ # Initial enrollment
+ if not self.enroll():
+ print("❌ Initial enrollment failed. Exiting.")
+ return
+
+ last_balance_check = 0
+ re_enroll_interval = 3600 # Re-enroll every hour
+ last_enroll = time.time()
+
+ try:
+ while True:
+ # Re-enroll periodically
+ if time.time() - last_enroll > re_enroll_interval:
+ print(f"\n🔄 Re-enrolling (periodic)...")
+ self.enroll()
+ last_enroll = time.time()
+
+ # Check lottery eligibility
+ eligible, info = self.check_lottery()
+
+ if eligible:
+ slot = info.get("slot", 0)
+ print(f"\n🎰 LOTTERY WIN! Slot {slot}")
+ self.submit_header(slot)
+
+ # Check balance every 5 minutes
+ if time.time() - last_balance_check > 300:
+ self.check_balance()
+ last_balance_check = time.time()
+ print(f"📊 Mining stats: {self.shares_accepted}/{self.shares_submitted} headers accepted")
+
+ time.sleep(LOTTERY_CHECK_INTERVAL)
+
+ except KeyboardInterrupt:
+ print(f"\n\n⛔ Mining stopped")
+ print(f" Wallet: {self.wallet}")
+ print(f" Headers: {self.shares_accepted}/{self.shares_submitted} accepted")
+ self.check_balance()
+
+def main():
+ import argparse
+ parser = argparse.ArgumentParser(description="RustChain G4 Miner - FIXED")
+ parser.add_argument("--id", default="dual-g4-125", help="Miner ID")
+ parser.add_argument("--wallet", help="Wallet address")
+ args = parser.parse_args()
+
+ miner = G4Miner(miner_id=args.id, wallet=args.wallet)
+ miner.mine_forever()
+
+if __name__ == "__main__":
+ main()
+ def _detect_hardware(self):
+ """Best-effort hardware survey on Mac OS X Tiger/Leopard."""
+ info = {
+ "family": "PowerPC",
+ "arch": "G4",
+ "model": "PowerMac",
+ "cpu": "PowerPC G4",
+ "cores": 1,
+ "memory_gb": 2,
+ "hostname": os.uname()[1]
+ }
+
+ try:
+ hw_raw = subprocess.check_output(
+ ["system_profiler", "SPHardwareDataType"],
+ stderr=subprocess.DEVNULL
+ ).decode("utf-8", "ignore")
+ m = re.search(r"Machine Model:\s*(.+)", hw_raw)
+ if m:
+ info["model"] = m.group(1).strip()
+ m = re.search(r"CPU Type:\s*(.+)", hw_raw)
+ if m:
+ info["cpu"] = m.group(1).strip()
+ m = re.search(r"Total Number Of Cores:\s*(\d+)", hw_raw, re.IGNORECASE)
+ if m:
+ info["cores"] = int(m.group(1))
+ m = re.search(r"Memory:\s*([\d\.]+)\s*GB", hw_raw)
+ if m:
+ info["memory_gb"] = float(m.group(1))
+ except Exception:
+ pass
+
+ info["macs"] = self._get_mac_addresses()
+ info["mac"] = info["macs"][0]
+ return info
+
+ def _get_mac_addresses(self):
+ macs = []
+ try:
+ output = subprocess.check_output(
+ ["/sbin/ifconfig", "-a"],
+ stderr=subprocess.DEVNULL
+ ).decode("utf-8", "ignore").splitlines()
+ for line in output:
+ m = re.search(r"ether\s+([0-9a-f:]{17})", line, re.IGNORECASE)
+ if m:
+ mac = m.group(1).lower()
+ if mac != "00:00:00:00:00:00":
+ macs.append(mac)
+ except Exception:
+ pass
+ return macs or ["00:0d:93:12:34:56"]
+
+ def _collect_entropy(self, cycles=48, inner=15000):
+ samples = []
+ for _ in range(cycles):
+ start = time.perf_counter_ns()
+ acc = 0
+ for j in range(inner):
+ acc ^= (j * 17) & 0xFFFFFFFF
+ duration = time.perf_counter_ns() - start
+ samples.append(duration)
+
+ mean_ns = sum(samples) / len(samples)
+ variance_ns = statistics.pvariance(samples) if len(samples) > 1 else 0.0
+ return {
+ "mean_ns": mean_ns,
+ "variance_ns": variance_ns,
+ "min_ns": min(samples),
+ "max_ns": max(samples),
+ "sample_count": len(samples),
+ "samples_preview": samples[:12],
+ }
diff --git a/wallet/rustchain_wallet_ppc.py b/wallet/rustchain_wallet_ppc.py
index 00ac24d8..02768ccc 100644
--- a/wallet/rustchain_wallet_ppc.py
+++ b/wallet/rustchain_wallet_ppc.py
@@ -107,7 +107,7 @@ def dumps(self, obj):
sys.exit(1)
# Configuration
-NODE_URL = "http://50.28.86.131:8088"
+NODE_URL = "https://50.28.86.131"
WALLET_FILE = os.path.expanduser("~/.rustchain_wallet")
class RustChainWallet:
diff --git a/wallet/rustchain_wallet_ppc.py.tmp b/wallet/rustchain_wallet_ppc.py.tmp
new file mode 100644
index 00000000..00ac24d8
--- /dev/null
+++ b/wallet/rustchain_wallet_ppc.py.tmp
@@ -0,0 +1,316 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+RustChain Wallet for PowerPC Macs (Tiger/Leopard)
+Requires: Python 2.3+ with Tkinter (included in Mac OS X)
+
+Usage: python rustchain_wallet_ppc.py [wallet_address]
+"""
+
+import os
+import sys
+import hashlib
+import urllib
+import urllib2
+import socket
+
+# Set default socket timeout for Python 2.3 compatibility
+# (urllib2.urlopen timeout param added in Python 2.6)
+socket.setdefaulttimeout(15)
+
+# JSON support for Python 2.3-2.5 (json module added in 2.6)
+try:
+ import json
+except ImportError:
+ try:
+ import simplejson as json
+ except ImportError:
+ # Manual JSON parsing for Python 2.3
+ class SimpleJSON:
+ def loads(self, s):
+ """Very basic JSON parser for simple objects"""
+ s = s.strip()
+ if s.startswith('{') and s.endswith('}'):
+ result = {}
+ s = s[1:-1].strip()
+ if not s:
+ return result
+ # Split by commas (simple case)
+ pairs = []
+ depth = 0
+ current = ""
+ for c in s:
+ if c in '{[':
+ depth += 1
+ elif c in '}]':
+ depth -= 1
+ if c == ',' and depth == 0:
+ pairs.append(current.strip())
+ current = ""
+ else:
+ current += c
+ if current.strip():
+ pairs.append(current.strip())
+
+ for pair in pairs:
+ if ':' in pair:
+ key, value = pair.split(':', 1)
+ key = key.strip().strip('"')
+ value = value.strip()
+ if value.startswith('"') and value.endswith('"'):
+ value = value[1:-1]
+ elif value == 'true':
+ value = True
+ elif value == 'false':
+ value = False
+ elif value == 'null':
+ value = None
+ else:
+ try:
+ if '.' in value:
+ value = float(value)
+ else:
+ value = int(value)
+ except:
+ pass
+ result[key] = value
+ return result
+ return {}
+
+ def dumps(self, obj):
+ """Very basic JSON serializer"""
+ if isinstance(obj, dict):
+ pairs = []
+ for k, v in obj.items():
+ pairs.append('"%s": %s' % (k, self.dumps(v)))
+ return '{%s}' % ', '.join(pairs)
+ elif isinstance(obj, (list, tuple)):
+ return '[%s]' % ', '.join(self.dumps(x) for x in obj)
+ elif isinstance(obj, str):
+ return '"%s"' % obj
+ elif isinstance(obj, bool):
+ return 'true' if obj else 'false'
+ elif obj is None:
+ return 'null'
+ else:
+ return str(obj)
+
+ json = SimpleJSON()
+
+# Tkinter import (Python 2 style)
+try:
+ import Tkinter as tk
+ import tkMessageBox
+ import tkSimpleDialog
+except ImportError:
+ print "Error: Tkinter not available"
+ sys.exit(1)
+
+# Configuration
+NODE_URL = "http://50.28.86.131:8088"
+WALLET_FILE = os.path.expanduser("~/.rustchain_wallet")
+
+class RustChainWallet:
+ def __init__(self, root):
+ self.root = root
+ self.root.title("RustChain Wallet - PPC Edition")
+ self.root.geometry("500x400")
+
+ # Try to load or generate wallet
+ self.wallet_address = self.load_or_create_wallet()
+
+ self.create_widgets()
+ self.refresh_balance()
+
+ def load_or_create_wallet(self):
+ """Load existing wallet or create new one"""
+ if os.path.exists(WALLET_FILE):
+ try:
+ f = open(WALLET_FILE, 'r')
+ addr = f.read().strip()
+ f.close()
+ if addr:
+ return addr
+ except:
+ pass
+
+ # Generate deterministic wallet from hostname
+ hostname = os.uname()[1]
+ miner_id = "ppc-wallet-%s" % hostname
+ wallet_hash = hashlib.sha256(miner_id).hexdigest()[:40]
+ wallet_addr = "%sRTC" % wallet_hash
+
+ # Save it
+ try:
+ f = open(WALLET_FILE, 'w')
+ f.write(wallet_addr)
+ f.close()
+ except:
+ pass
+
+ return wallet_addr
+
+ def create_widgets(self):
+ # Title
+ title = tk.Label(self.root, text="RustChain Wallet", font=("Helvetica", 18, "bold"))
+ title.pack(pady=10)
+
+ # Wallet Address Frame
+ addr_frame = tk.LabelFrame(self.root, text="Your Wallet Address", padx=10, pady=10)
+ addr_frame.pack(fill="x", padx=20, pady=10)
+
+ self.addr_var = tk.StringVar()
+ self.addr_var.set(self.wallet_address)
+ addr_entry = tk.Entry(addr_frame, textvariable=self.addr_var, width=50, state="readonly")
+ addr_entry.pack(fill="x")
+
+ copy_btn = tk.Button(addr_frame, text="Copy Address", command=self.copy_address)
+ copy_btn.pack(pady=5)
+
+ # Balance Frame
+ bal_frame = tk.LabelFrame(self.root, text="Balance", padx=10, pady=10)
+ bal_frame.pack(fill="x", padx=20, pady=10)
+
+ self.balance_var = tk.StringVar()
+ self.balance_var.set("Loading...")
+ balance_label = tk.Label(bal_frame, textvariable=self.balance_var, font=("Helvetica", 24, "bold"))
+ balance_label.pack()
+
+ refresh_btn = tk.Button(bal_frame, text="Refresh Balance", command=self.refresh_balance)
+ refresh_btn.pack(pady=5)
+
+ # Send Frame
+ send_frame = tk.LabelFrame(self.root, text="Send RTC", padx=10, pady=10)
+ send_frame.pack(fill="x", padx=20, pady=10)
+
+ # To address
+ to_label = tk.Label(send_frame, text="To Address:")
+ to_label.grid(row=0, column=0, sticky="e", padx=5, pady=2)
+
+ self.to_entry = tk.Entry(send_frame, width=45)
+ self.to_entry.grid(row=0, column=1, padx=5, pady=2)
+
+ # Amount
+ amt_label = tk.Label(send_frame, text="Amount (RTC):")
+ amt_label.grid(row=1, column=0, sticky="e", padx=5, pady=2)
+
+ self.amt_entry = tk.Entry(send_frame, width=20)
+ self.amt_entry.grid(row=1, column=1, sticky="w", padx=5, pady=2)
+
+ send_btn = tk.Button(send_frame, text="Send RTC", command=self.send_rtc)
+ send_btn.grid(row=2, column=1, pady=10)
+
+ # Status bar
+ self.status_var = tk.StringVar()
+ self.status_var.set("Connected to: %s" % NODE_URL)
+ status_bar = tk.Label(self.root, textvariable=self.status_var, relief="sunken", anchor="w")
+ status_bar.pack(side="bottom", fill="x")
+
+ def copy_address(self):
+ """Copy wallet address to clipboard"""
+ self.root.clipboard_clear()
+ self.root.clipboard_append(self.wallet_address)
+ self.status_var.set("Address copied to clipboard!")
+
+ def refresh_balance(self):
+ """Fetch balance from node"""
+ self.status_var.set("Fetching balance...")
+ self.root.update()
+
+ try:
+ url = "%s/balance/%s" % (NODE_URL, self.wallet_address)
+ response = urllib2.urlopen(url)
+ data = json.loads(response.read())
+
+ # Server returns balance_rtc directly in RTC
+ balance_rtc = data.get("balance_rtc", 0)
+ if balance_rtc is None:
+ balance_rtc = 0
+ balance_rtc = float(balance_rtc)
+
+ self.balance_var.set("%.4f RTC" % balance_rtc)
+ self.status_var.set("Balance updated")
+ except Exception, e:
+ self.balance_var.set("Error")
+ self.status_var.set("Error: %s" % str(e))
+
+ def send_rtc(self):
+ """Send RTC to another address"""
+ to_addr = self.to_entry.get().strip()
+ amount_str = self.amt_entry.get().strip()
+
+ if not to_addr:
+ tkMessageBox.showerror("Error", "Please enter a recipient address")
+ return
+
+ if not amount_str:
+ tkMessageBox.showerror("Error", "Please enter an amount")
+ return
+
+ try:
+ amount = float(amount_str)
+ except:
+ tkMessageBox.showerror("Error", "Invalid amount")
+ return
+
+ if amount <= 0:
+ tkMessageBox.showerror("Error", "Amount must be positive")
+ return
+
+ # Confirm
+ msg = "Send %.4f RTC to\n%s?" % (amount, to_addr)
+ if not tkMessageBox.askyesno("Confirm Send", msg):
+ return
+
+ self.status_var.set("Sending transaction...")
+ self.root.update()
+
+ try:
+ # Build transaction payload
+ payload = {
+ "from": self.wallet_address,
+ "to": to_addr,
+ "amount": int(amount * 1000000), # Convert to micro-RTC
+ "memo": "PPC Wallet Transfer"
+ }
+
+ url = "%s/wallet/transfer" % NODE_URL
+ req = urllib2.Request(url, json.dumps(payload))
+ req.add_header("Content-Type", "application/json")
+
+ response = urllib2.urlopen(req)
+ result = json.loads(response.read())
+
+ if result.get("ok"):
+ tkMessageBox.showinfo("Success", "Transaction sent successfully!")
+ self.to_entry.delete(0, tk.END)
+ self.amt_entry.delete(0, tk.END)
+ self.refresh_balance()
+ else:
+ error = result.get("error", "Unknown error")
+ tkMessageBox.showerror("Error", "Transaction failed: %s" % error)
+ except Exception, e:
+ tkMessageBox.showerror("Error", "Transaction failed: %s" % str(e))
+
+ self.status_var.set("Ready")
+
+def main():
+ root = tk.Tk()
+
+ # Set wallet address from command line if provided
+ if len(sys.argv) > 1:
+ global WALLET_FILE
+ # Write provided address to wallet file
+ addr = sys.argv[1]
+ try:
+ f = open(WALLET_FILE, 'w')
+ f.write(addr)
+ f.close()
+ except:
+ pass
+
+ app = RustChainWallet(root)
+ root.mainloop()
+
+if __name__ == "__main__":
+ main()
From 0fe5979ad613b9e24586f8e59ac05574a07553ac Mon Sep 17 00:00:00 2001
From: Scott
Date: Sat, 28 Feb 2026 10:19:29 -0600
Subject: [PATCH 09/25] test: add attestation fuzz testing
---
node/rustchain_v2_integrated_v2.2.1_rip200.py | 111 +++++++++--
.../invalid_root_array.json | 5 +
.../attestation_corpus/invalid_root_null.json | 1 +
.../malformed_device_scalar.json | 13 ++
.../malformed_fingerprint_checks_array.json | 20 ++
.../malformed_signals_macs_object.json | 17 ++
.../malformed_signals_scalar.json | 12 ++
tests/test_attestation_fuzz.py | 188 ++++++++++++++++++
8 files changed, 354 insertions(+), 13 deletions(-)
create mode 100644 tests/attestation_corpus/invalid_root_array.json
create mode 100644 tests/attestation_corpus/invalid_root_null.json
create mode 100644 tests/attestation_corpus/malformed_device_scalar.json
create mode 100644 tests/attestation_corpus/malformed_fingerprint_checks_array.json
create mode 100644 tests/attestation_corpus/malformed_signals_macs_object.json
create mode 100644 tests/attestation_corpus/malformed_signals_scalar.json
create mode 100644 tests/test_attestation_fuzz.py
diff --git a/node/rustchain_v2_integrated_v2.2.1_rip200.py b/node/rustchain_v2_integrated_v2.2.1_rip200.py
index e71eb1b0..a900a449 100644
--- a/node/rustchain_v2_integrated_v2.2.1_rip200.py
+++ b/node/rustchain_v2_integrated_v2.2.1_rip200.py
@@ -219,6 +219,84 @@ def _parse_int_query_arg(name: str, default: int, min_value: int | None = None,
return value, None
+
+def _attest_mapping(value):
+ """Return a dict-like payload section or an empty mapping."""
+ return value if isinstance(value, dict) else {}
+
+
+def _attest_text(value):
+ """Accept only non-empty text values from untrusted attestation input."""
+ if isinstance(value, str):
+ value = value.strip()
+ if value:
+ return value
+ return None
+
+
+def _attest_positive_int(value, default=1):
+ """Coerce untrusted integer-like values to a safe positive integer."""
+ try:
+ coerced = int(value)
+ except (TypeError, ValueError):
+ return default
+ return coerced if coerced > 0 else default
+
+
+def _attest_string_list(value):
+ """Coerce a list-like field into a list of non-empty strings."""
+ if not isinstance(value, list):
+ return []
+ items = []
+ for item in value:
+ text = _attest_text(item)
+ if text:
+ items.append(text)
+ return items
+
+
+def _normalize_attestation_device(device):
+ """Shallow-normalize device metadata so malformed JSON shapes fail closed."""
+ raw = _attest_mapping(device)
+ normalized = {"cores": _attest_positive_int(raw.get("cores"), default=1)}
+ for field in (
+ "device_family",
+ "family",
+ "device_arch",
+ "arch",
+ "device_model",
+ "model",
+ "cpu",
+ "serial_number",
+ "serial",
+ ):
+ text = _attest_text(raw.get(field))
+ if text is not None:
+ normalized[field] = text
+ return normalized
+
+
+def _normalize_attestation_signals(signals):
+ """Shallow-normalize signal metadata used by attestation validation."""
+ raw = _attest_mapping(signals)
+ normalized = {"macs": _attest_string_list(raw.get("macs"))}
+ for field in ("hostname", "serial"):
+ text = _attest_text(raw.get(field))
+ if text is not None:
+ normalized[field] = text
+ return normalized
+
+
+def _normalize_attestation_report(report):
+ """Normalize report metadata used by challenge/ticket handling."""
+ raw = _attest_mapping(report)
+ normalized = {}
+ for field in ("nonce", "commitment"):
+ text = _attest_text(raw.get(field))
+ if text is not None:
+ normalized[field] = text
+ return normalized
+
# Register Hall of Rust blueprint (tables initialized after DB_PATH is set)
try:
from hall_of_rust import hall_bp
@@ -1223,7 +1301,9 @@ def validate_fingerprint_data(fingerprint: dict, claimed_device: dict = None) ->
return False, "missing_fingerprint_data"
checks = fingerprint.get("checks", {})
- claimed_device = claimed_device or {}
+ if not isinstance(checks, dict):
+ checks = {}
+ claimed_device = claimed_device if isinstance(claimed_device, dict) else {}
def get_check_status(check_data):
"""Handle both bool and dict formats for check results"""
@@ -2009,17 +2089,23 @@ def _check_hardware_binding(miner_id: str, device: dict, signals: dict = None, s
@app.route('/attest/submit', methods=['POST'])
def submit_attestation():
"""Submit hardware attestation with fingerprint validation"""
- data = request.get_json()
+ data = request.get_json(silent=True)
+ if not isinstance(data, dict):
+ return jsonify({
+ "ok": False,
+ "error": "invalid_json_object",
+ "message": "Expected a JSON object request body",
+ "code": "INVALID_JSON_OBJECT"
+ }), 400
# Extract client IP (handle nginx proxy)
client_ip = client_ip_from_request(request)
# Extract attestation data
- miner = data.get('miner') or data.get('miner_id')
- report = data.get('report', {})
- nonce = report.get('nonce') or data.get('nonce')
- challenge = report.get('challenge') or data.get('challenge')
- device = data.get('device', {})
+ miner = _attest_text(data.get('miner')) or _attest_text(data.get('miner_id'))
+ report = _normalize_attestation_report(data.get('report'))
+ nonce = report.get('nonce') or _attest_text(data.get('nonce'))
+ device = _normalize_attestation_device(data.get('device'))
# IP rate limiting (Security Hardening 2026-02-02)
ip_ok, ip_reason = check_ip_rate_limit(client_ip, miner)
@@ -2031,8 +2117,8 @@ def submit_attestation():
"message": "Too many unique miners from this IP address",
"code": "IP_RATE_LIMIT"
}), 429
- signals = data.get('signals', {})
- fingerprint = data.get('fingerprint', {}) # NEW: Extract fingerprint
+ signals = _normalize_attestation_signals(data.get('signals'))
+ fingerprint = _attest_mapping(data.get('fingerprint')) # NEW: Extract fingerprint
# Basic validation
if not miner:
@@ -2089,9 +2175,9 @@ def submit_attestation():
# SECURITY: Hardware binding check v2.0 (serial + entropy validation)
serial = device.get('serial_number') or device.get('serial') or signals.get('serial')
- cores = device.get('cores', 1)
- arch = device.get('arch') or device.get('device_arch', 'modern')
- macs = signals.get('macs', [])
+ cores = _attest_positive_int(device.get('cores'), default=1)
+ arch = _attest_text(device.get('arch')) or _attest_text(device.get('device_arch')) or 'modern'
+ macs = _attest_string_list(signals.get('macs'))
if HW_BINDING_V2 and serial:
hw_ok, hw_msg, hw_details = bind_hardware_v2(
@@ -2124,7 +2210,6 @@ def submit_attestation():
}), 409
# RIP-0147a: Check OUI gate
- macs = signals.get('macs', [])
if macs:
oui_ok, oui_info = _check_oui_gate(macs)
if not oui_ok:
diff --git a/tests/attestation_corpus/invalid_root_array.json b/tests/attestation_corpus/invalid_root_array.json
new file mode 100644
index 00000000..3953b3f8
--- /dev/null
+++ b/tests/attestation_corpus/invalid_root_array.json
@@ -0,0 +1,5 @@
+[
+ {
+ "miner": "array-root-miner"
+ }
+]
diff --git a/tests/attestation_corpus/invalid_root_null.json b/tests/attestation_corpus/invalid_root_null.json
new file mode 100644
index 00000000..19765bd5
--- /dev/null
+++ b/tests/attestation_corpus/invalid_root_null.json
@@ -0,0 +1 @@
+null
diff --git a/tests/attestation_corpus/malformed_device_scalar.json b/tests/attestation_corpus/malformed_device_scalar.json
new file mode 100644
index 00000000..1e97b0e6
--- /dev/null
+++ b/tests/attestation_corpus/malformed_device_scalar.json
@@ -0,0 +1,13 @@
+{
+ "miner": "device-scalar-miner",
+ "device": "not-a-device-object",
+ "signals": {
+ "hostname": "device-scalar-host",
+ "macs": [
+ "AA:BB:CC:DD:EE:01"
+ ]
+ },
+ "report": {
+ "commitment": "device-scalar-commitment"
+ }
+}
diff --git a/tests/attestation_corpus/malformed_fingerprint_checks_array.json b/tests/attestation_corpus/malformed_fingerprint_checks_array.json
new file mode 100644
index 00000000..1229b47c
--- /dev/null
+++ b/tests/attestation_corpus/malformed_fingerprint_checks_array.json
@@ -0,0 +1,20 @@
+{
+ "miner": "fingerprint-array-miner",
+ "device": {
+ "device_family": "PowerPC",
+ "device_arch": "power8",
+ "cores": 8
+ },
+ "signals": {
+ "hostname": "fingerprint-array-host",
+ "macs": [
+ "AA:BB:CC:DD:EE:02"
+ ]
+ },
+ "fingerprint": {
+ "checks": []
+ },
+ "report": {
+ "commitment": "fingerprint-array-commitment"
+ }
+}
diff --git a/tests/attestation_corpus/malformed_signals_macs_object.json b/tests/attestation_corpus/malformed_signals_macs_object.json
new file mode 100644
index 00000000..7cfacdd6
--- /dev/null
+++ b/tests/attestation_corpus/malformed_signals_macs_object.json
@@ -0,0 +1,17 @@
+{
+ "miner": "macs-object-miner",
+ "device": {
+ "device_family": "PowerPC",
+ "device_arch": "g4",
+ "cores": 4
+ },
+ "signals": {
+ "hostname": "macs-object-host",
+ "macs": {
+ "primary": "AA:BB:CC:DD:EE:03"
+ }
+ },
+ "report": {
+ "commitment": "macs-object-commitment"
+ }
+}
diff --git a/tests/attestation_corpus/malformed_signals_scalar.json b/tests/attestation_corpus/malformed_signals_scalar.json
new file mode 100644
index 00000000..4b29c96e
--- /dev/null
+++ b/tests/attestation_corpus/malformed_signals_scalar.json
@@ -0,0 +1,12 @@
+{
+ "miner": "signals-scalar-miner",
+ "device": {
+ "device_family": "PowerPC",
+ "device_arch": "power9",
+ "cores": 6
+ },
+ "signals": "not-a-signals-object",
+ "report": {
+ "commitment": "signals-scalar-commitment"
+ }
+}
diff --git a/tests/test_attestation_fuzz.py b/tests/test_attestation_fuzz.py
new file mode 100644
index 00000000..d90fb1ab
--- /dev/null
+++ b/tests/test_attestation_fuzz.py
@@ -0,0 +1,188 @@
+import json
+import os
+import random
+import sqlite3
+import sys
+import uuid
+from pathlib import Path
+
+import pytest
+
+integrated_node = sys.modules["integrated_node"]
+
+CORPUS_DIR = Path(__file__).parent / "attestation_corpus"
+
+
+def _init_attestation_db(db_path: Path) -> None:
+ conn = sqlite3.connect(db_path)
+ conn.executescript(
+ """
+ CREATE TABLE blocked_wallets (
+ wallet TEXT PRIMARY KEY,
+ reason TEXT
+ );
+ CREATE TABLE balances (
+ miner_pk TEXT PRIMARY KEY,
+ balance_rtc REAL DEFAULT 0
+ );
+ CREATE TABLE epoch_enroll (
+ epoch INTEGER NOT NULL,
+ miner_pk TEXT NOT NULL,
+ weight REAL NOT NULL,
+ PRIMARY KEY (epoch, miner_pk)
+ );
+ CREATE TABLE miner_header_keys (
+ miner_id TEXT PRIMARY KEY,
+ pubkey_hex TEXT
+ );
+ CREATE TABLE tickets (
+ ticket_id TEXT PRIMARY KEY,
+ expires_at INTEGER NOT NULL,
+ commitment TEXT
+ );
+ CREATE TABLE oui_deny (
+ oui TEXT PRIMARY KEY,
+ vendor TEXT,
+ enforce INTEGER DEFAULT 0
+ );
+ """
+ )
+ conn.commit()
+ conn.close()
+
+
+def _base_payload() -> dict:
+ return {
+ "miner": "fuzz-miner",
+ "device": {
+ "device_family": "PowerPC",
+ "device_arch": "power8",
+ "cores": 8,
+ "cpu": "IBM POWER8",
+ "serial_number": "SERIAL-123",
+ },
+ "signals": {
+ "hostname": "power8-host",
+ "macs": ["AA:BB:CC:DD:EE:10"],
+ },
+ "report": {
+ "nonce": "nonce-123",
+ "commitment": "commitment-123",
+ },
+ "fingerprint": {
+ "checks": {
+ "anti_emulation": {
+ "passed": True,
+ "data": {"vm_indicators": [], "paths_checked": ["/proc/cpuinfo"]},
+ },
+ "clock_drift": {
+ "passed": True,
+ "data": {"drift_ms": 0},
+ },
+ }
+ },
+ }
+
+
+@pytest.fixture
+def client(monkeypatch):
+ local_tmp_dir = Path(__file__).parent / ".tmp_attestation"
+ local_tmp_dir.mkdir(exist_ok=True)
+ db_path = local_tmp_dir / f"{uuid.uuid4().hex}.sqlite3"
+ _init_attestation_db(db_path)
+
+ monkeypatch.setattr(integrated_node, "DB_PATH", str(db_path))
+ monkeypatch.setattr(integrated_node, "HW_BINDING_V2", False, raising=False)
+ monkeypatch.setattr(integrated_node, "HW_PROOF_AVAILABLE", False, raising=False)
+ monkeypatch.setattr(integrated_node, "check_ip_rate_limit", lambda client_ip, miner_id: (True, "ok"))
+ monkeypatch.setattr(integrated_node, "_check_hardware_binding", lambda *args, **kwargs: (True, "ok", ""))
+ monkeypatch.setattr(integrated_node, "record_attestation_success", lambda *args, **kwargs: None)
+ monkeypatch.setattr(integrated_node, "record_macs", lambda *args, **kwargs: None)
+ monkeypatch.setattr(integrated_node, "current_slot", lambda: 12345)
+ monkeypatch.setattr(integrated_node, "slot_to_epoch", lambda slot: 85)
+
+ integrated_node.app.config["TESTING"] = True
+ with integrated_node.app.test_client() as test_client:
+ yield test_client
+
+ if db_path.exists():
+ try:
+ db_path.unlink()
+ except PermissionError:
+ pass
+
+
+def _post_raw_json(client, raw_json: str):
+ return client.post("/attest/submit", data=raw_json, content_type="application/json")
+
+
+@pytest.mark.parametrize(
+ ("file_name", "expected_status"),
+ [
+ ("invalid_root_null.json", 400),
+ ("invalid_root_array.json", 400),
+ ],
+)
+def test_attest_submit_rejects_non_object_json(client, file_name, expected_status):
+ response = _post_raw_json(client, (CORPUS_DIR / file_name).read_text(encoding="utf-8"))
+
+ assert response.status_code == expected_status
+ data = response.get_json()
+ assert data["code"] == "INVALID_JSON_OBJECT"
+
+
+@pytest.mark.parametrize(
+ "file_name",
+ [
+ "malformed_device_scalar.json",
+ "malformed_signals_scalar.json",
+ "malformed_signals_macs_object.json",
+ "malformed_fingerprint_checks_array.json",
+ ],
+)
+def test_attest_submit_corpus_cases_do_not_raise_server_errors(client, file_name):
+ response = _post_raw_json(client, (CORPUS_DIR / file_name).read_text(encoding="utf-8"))
+
+ assert response.status_code < 500
+ assert response.get_json()["ok"] is True
+
+
+def _mutate_payload(rng: random.Random) -> dict:
+ payload = _base_payload()
+ mutation = rng.randrange(8)
+
+ if mutation == 0:
+ payload["miner"] = ["not", "a", "string"]
+ elif mutation == 1:
+ payload["device"] = "not-a-device-object"
+ elif mutation == 2:
+ payload["device"]["cores"] = rng.choice([0, -1, "NaN", [], {}])
+ elif mutation == 3:
+ payload["signals"] = "not-a-signals-object"
+ elif mutation == 4:
+ payload["signals"]["macs"] = rng.choice(
+ [
+ {"primary": "AA:BB:CC:DD:EE:99"},
+ "AA:BB:CC:DD:EE:99",
+ [None, 123, "AA:BB:CC:DD:EE:99"],
+ ]
+ )
+ elif mutation == 5:
+ payload["report"] = rng.choice(["not-a-report-object", [], {"commitment": ["bad"]}])
+ elif mutation == 6:
+ payload["fingerprint"] = {"checks": rng.choice([[], "bad", {"anti_emulation": True}])}
+ else:
+ payload["device"]["cpu"] = rng.choice(["qemu-system-ppc", "IBM POWER8", None, ["nested"]])
+ payload["signals"]["hostname"] = rng.choice(["vmware-host", "power8-host", None, ["nested"]])
+
+ return payload
+
+
+def test_attest_submit_fuzz_no_unhandled_exceptions(client):
+ cases = int(os.getenv("ATTEST_FUZZ_CASES", "250"))
+ rng = random.Random(475)
+
+ for index in range(cases):
+ payload = _mutate_payload(rng)
+ response = client.post("/attest/submit", json=payload)
+ assert response.status_code < 500, f"case={index} payload={payload!r}"
From 1f83ebffbca71000504474404ea4214be22b2a99 Mon Sep 17 00:00:00 2001
From: AutoJanitor <121303252+Scottcjn@users.noreply.github.com>
Date: Sat, 28 Feb 2026 10:29:20 -0600
Subject: [PATCH 10/25] =?UTF-8?q?miners/macos:=20v2.5.0=20=E2=80=94=20embe?=
=?UTF-8?q?dded=20TLS=20proxy=20fallback=20for=20legacy=20Macs?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
miners/macos/rustchain_mac_miner_v2.5.py | 680 +++++++++++++++++++++++
1 file changed, 680 insertions(+)
create mode 100644 miners/macos/rustchain_mac_miner_v2.5.py
diff --git a/miners/macos/rustchain_mac_miner_v2.5.py b/miners/macos/rustchain_mac_miner_v2.5.py
new file mode 100644
index 00000000..2dd7d728
--- /dev/null
+++ b/miners/macos/rustchain_mac_miner_v2.5.py
@@ -0,0 +1,680 @@
+#!/usr/bin/env python3
+"""
+RustChain Mac Universal Miner v2.5.0
+Supports: Apple Silicon (M1/M2/M3), Intel Mac, PowerPC (G4/G5)
+With RIP-PoA Hardware Fingerprint Attestation + Serial Binding v2.0
++ Embedded TLS Proxy Fallback for Legacy Macs (Tiger/Leopard)
+
+New in v2.5:
+ - Auto-detect TLS capability: try HTTPS direct, fall back to HTTP proxy
+ - Proxy auto-discovery on LAN (192.168.0.160:8089)
+ - Python 3.7+ compatible (no walrus, no f-string =)
+ - Persistent launchd/cron integration helpers
+ - Sleep-resistant: re-attest on wake automatically
+"""
+import warnings
+warnings.filterwarnings('ignore', message='Unverified HTTPS request')
+
+import os
+import sys
+import json
+import time
+import hashlib
+import platform
+import subprocess
+import statistics
+import re
+import socket
+from datetime import datetime
+
+# Color helper stubs (no-op if terminal doesn't support ANSI)
+def info(msg): return msg
+def warning(msg): return msg
+def success(msg): return msg
+def error(msg): return msg
+
+# Attempt to import requests; provide instructions if missing
+try:
+ import requests
+except ImportError:
+ print("[ERROR] 'requests' module not found.")
+ print(" Install with: pip3 install requests --user")
+ print(" Or: python3 -m pip install requests --user")
+ sys.exit(1)
+
+# Import fingerprint checks
+try:
+ from fingerprint_checks import validate_all_checks
+ FINGERPRINT_AVAILABLE = True
+except ImportError:
+ FINGERPRINT_AVAILABLE = False
+ print(warning("[WARN] fingerprint_checks.py not found - fingerprint attestation disabled"))
+
+# Import CPU architecture detection
+try:
+ from cpu_architecture_detection import detect_cpu_architecture, calculate_antiquity_multiplier
+ CPU_DETECTION_AVAILABLE = True
+except ImportError:
+ CPU_DETECTION_AVAILABLE = False
+
+MINER_VERSION = "2.5.0"
+NODE_URL = os.environ.get("RUSTCHAIN_NODE", "https://50.28.86.131")
+PROXY_URL = os.environ.get("RUSTCHAIN_PROXY", "http://192.168.0.160:8089")
+BLOCK_TIME = 600 # 10 minutes
+LOTTERY_CHECK_INTERVAL = 10
+ATTESTATION_TTL = 580 # Re-attest 20s before expiry
+
+
+# ── Transport Layer (HTTPS direct or HTTP proxy) ────────────────────
+
+class NodeTransport:
+ """Handles communication with the RustChain node.
+
+ Tries HTTPS directly first. If TLS fails (old Python/OpenSSL on
+ Tiger/Leopard), falls back to the HTTP proxy on the NAS.
+ """
+
+ def __init__(self, node_url, proxy_url):
+ self.node_url = node_url.rstrip("/")
+ self.proxy_url = proxy_url.rstrip("/") if proxy_url else None
+ self.use_proxy = False
+ self._probe_transport()
+
+ def _probe_transport(self):
+ """Test if we can reach the node directly via HTTPS."""
+ try:
+ r = requests.get(
+ self.node_url + "/health",
+ timeout=10, verify=False
+ )
+ if r.status_code == 200:
+ print(success("[TRANSPORT] Direct HTTPS to node: OK"))
+ self.use_proxy = False
+ return
+ except requests.exceptions.SSLError:
+ print(warning("[TRANSPORT] TLS failed (legacy OpenSSL?) - trying proxy..."))
+ except Exception as e:
+ print(warning("[TRANSPORT] Direct connection failed: {} - trying proxy...".format(e)))
+
+ # Try the proxy
+ if self.proxy_url:
+ try:
+ r = requests.get(
+ self.proxy_url + "/health",
+ timeout=10
+ )
+ if r.status_code == 200:
+ print(success("[TRANSPORT] HTTP proxy at {}: OK".format(self.proxy_url)))
+ self.use_proxy = True
+ return
+ except Exception as e:
+ print(warning("[TRANSPORT] Proxy {} also failed: {}".format(self.proxy_url, e)))
+
+ # Last resort: try direct without verify (may work on some old systems)
+ print(warning("[TRANSPORT] Falling back to direct HTTPS (verify=False)"))
+ self.use_proxy = False
+
+ @property
+ def base_url(self):
+ if self.use_proxy:
+ return self.proxy_url
+ return self.node_url
+
+ def get(self, path, **kwargs):
+ """GET request through whichever transport works."""
+ kwargs.setdefault("timeout", 15)
+ kwargs.setdefault("verify", False)
+ url = self.base_url + path
+ return requests.get(url, **kwargs)
+
+ def post(self, path, **kwargs):
+ """POST request through whichever transport works."""
+ kwargs.setdefault("timeout", 15)
+ kwargs.setdefault("verify", False)
+ url = self.base_url + path
+ return requests.post(url, **kwargs)
+
+
+# ── Hardware Detection ──────────────────────────────────────────────
+
+def get_mac_serial():
+ """Get hardware serial number for macOS systems."""
+ try:
+ result = subprocess.run(
+ ['system_profiler', 'SPHardwareDataType'],
+ capture_output=True, text=True, timeout=10
+ )
+ for line in result.stdout.split('\n'):
+ if 'Serial Number' in line:
+ return line.split(':')[1].strip()
+ except Exception:
+ pass
+
+ try:
+ result = subprocess.run(
+ ['ioreg', '-l'],
+ capture_output=True, text=True, timeout=10
+ )
+ for line in result.stdout.split('\n'):
+ if 'IOPlatformSerialNumber' in line:
+ return line.split('"')[-2]
+ except Exception:
+ pass
+
+ try:
+ result = subprocess.run(
+ ['system_profiler', 'SPHardwareDataType'],
+ capture_output=True, text=True, timeout=10
+ )
+ for line in result.stdout.split('\n'):
+ if 'Hardware UUID' in line:
+ return line.split(':')[1].strip()[:16]
+ except Exception:
+ pass
+
+ return None
+
+
+def detect_hardware():
+ """Auto-detect Mac hardware architecture."""
+ machine = platform.machine().lower()
+
+ hw_info = {
+ "family": "unknown",
+ "arch": "unknown",
+ "model": "Mac",
+ "cpu": "unknown",
+ "cores": os.cpu_count() or 1,
+ "memory_gb": 4,
+ "hostname": platform.node(),
+ "mac": "00:00:00:00:00:00",
+ "macs": [],
+ "serial": get_mac_serial()
+ }
+
+ # Get MAC addresses
+ try:
+ result = subprocess.run(['ifconfig'], capture_output=True, text=True, timeout=5)
+ macs = re.findall(r'ether\s+([0-9a-f:]{17})', result.stdout, re.IGNORECASE)
+ hw_info["macs"] = macs if macs else ["00:00:00:00:00:00"]
+ hw_info["mac"] = macs[0] if macs else "00:00:00:00:00:00"
+ except Exception:
+ pass
+
+ # Get memory
+ try:
+ result = subprocess.run(['sysctl', '-n', 'hw.memsize'],
+ capture_output=True, text=True, timeout=5)
+ hw_info["memory_gb"] = int(result.stdout.strip()) // (1024**3)
+ except Exception:
+ pass
+
+ # Apple Silicon Detection (M1/M2/M3/M4)
+ if machine == 'arm64':
+ hw_info["family"] = "Apple Silicon"
+ try:
+ result = subprocess.run(['sysctl', '-n', 'machdep.cpu.brand_string'],
+ capture_output=True, text=True, timeout=5)
+ brand = result.stdout.strip()
+ hw_info["cpu"] = brand
+
+ if 'M4' in brand:
+ hw_info["arch"] = "M4"
+ elif 'M3' in brand:
+ hw_info["arch"] = "M3"
+ elif 'M2' in brand:
+ hw_info["arch"] = "M2"
+ elif 'M1' in brand:
+ hw_info["arch"] = "M1"
+ else:
+ hw_info["arch"] = "apple_silicon"
+ except Exception:
+ hw_info["arch"] = "apple_silicon"
+ hw_info["cpu"] = "Apple Silicon"
+
+ # Intel Mac Detection
+ elif machine == 'x86_64':
+ hw_info["family"] = "x86_64"
+ try:
+ result = subprocess.run(['sysctl', '-n', 'machdep.cpu.brand_string'],
+ capture_output=True, text=True, timeout=5)
+ cpu_brand = result.stdout.strip()
+ hw_info["cpu"] = cpu_brand
+
+ if CPU_DETECTION_AVAILABLE:
+ cpu_info = calculate_antiquity_multiplier(cpu_brand)
+ hw_info["arch"] = cpu_info.architecture
+ hw_info["cpu_vendor"] = cpu_info.vendor
+ hw_info["cpu_year"] = cpu_info.microarch_year
+ hw_info["cpu_generation"] = cpu_info.generation
+ hw_info["is_server"] = cpu_info.is_server
+ else:
+ cpu_lower = cpu_brand.lower()
+ if 'core 2' in cpu_lower or 'core(tm)2' in cpu_lower:
+ hw_info["arch"] = "core2"
+ elif 'xeon' in cpu_lower and ('e5-16' in cpu_lower or 'e5-26' in cpu_lower):
+ hw_info["arch"] = "ivy_bridge"
+ elif 'i7-3' in cpu_lower or 'i5-3' in cpu_lower or 'i3-3' in cpu_lower:
+ hw_info["arch"] = "ivy_bridge"
+ elif 'i7-2' in cpu_lower or 'i5-2' in cpu_lower or 'i3-2' in cpu_lower:
+ hw_info["arch"] = "sandy_bridge"
+ elif 'i7-9' in cpu_lower and '900' in cpu_lower:
+ hw_info["arch"] = "nehalem"
+ elif 'i7-4' in cpu_lower or 'i5-4' in cpu_lower:
+ hw_info["arch"] = "haswell"
+ elif 'pentium' in cpu_lower:
+ hw_info["arch"] = "pentium4"
+ else:
+ hw_info["arch"] = "modern"
+ except Exception:
+ hw_info["arch"] = "modern"
+ hw_info["cpu"] = "Intel Mac"
+
+ # PowerPC Detection (for vintage Macs)
+ elif machine in ('ppc', 'ppc64', 'powerpc', 'powerpc64', 'Power Macintosh'):
+ hw_info["family"] = "PowerPC"
+ try:
+ result = subprocess.run(['system_profiler', 'SPHardwareDataType'],
+ capture_output=True, text=True, timeout=10)
+ output = result.stdout.lower()
+
+ if 'g5' in output or 'powermac11' in output:
+ hw_info["arch"] = "G5"
+ hw_info["cpu"] = "PowerPC G5"
+ elif 'g4' in output or 'powermac3' in output or 'powerbook' in output:
+ hw_info["arch"] = "G4"
+ hw_info["cpu"] = "PowerPC G4"
+ elif 'g3' in output:
+ hw_info["arch"] = "G3"
+ hw_info["cpu"] = "PowerPC G3"
+ else:
+ hw_info["arch"] = "G4"
+ hw_info["cpu"] = "PowerPC"
+ except Exception:
+ hw_info["arch"] = "G4"
+ hw_info["cpu"] = "PowerPC G4"
+
+ # Get model name
+ try:
+ result = subprocess.run(['system_profiler', 'SPHardwareDataType'],
+ capture_output=True, text=True, timeout=10)
+ for line in result.stdout.split('\n'):
+ if 'Model Name' in line or 'Model Identifier' in line:
+ hw_info["model"] = line.split(':')[1].strip()
+ break
+ except Exception:
+ pass
+
+ return hw_info
+
+
+def collect_entropy(cycles=48, inner_loop=25000):
+ """Collect timing entropy for hardware attestation."""
+ samples = []
+ for _ in range(cycles):
+ start = time.perf_counter_ns()
+ acc = 0
+ for j in range(inner_loop):
+ acc ^= (j * 31) & 0xFFFFFFFF
+ duration = time.perf_counter_ns() - start
+ samples.append(duration)
+
+ mean_ns = sum(samples) / len(samples)
+ variance_ns = statistics.pvariance(samples) if len(samples) > 1 else 0.0
+
+ return {
+ "mean_ns": mean_ns,
+ "variance_ns": variance_ns,
+ "min_ns": min(samples),
+ "max_ns": max(samples),
+ "sample_count": len(samples),
+ "samples_preview": samples[:12],
+ }
+
+
+# ── Miner Class ─────────────────────────────────────────────────────
+
+class MacMiner:
+ def __init__(self, miner_id=None, wallet=None, node_url=None, proxy_url=None):
+ self.hw_info = detect_hardware()
+ self.fingerprint_data = {}
+ self.fingerprint_passed = False
+
+ # Generate miner_id from hardware
+ if miner_id:
+ self.miner_id = miner_id
+ else:
+ hw_hash = hashlib.sha256(
+ "{}-{}".format(
+ self.hw_info['hostname'],
+ self.hw_info['serial'] or 'unknown'
+ ).encode()
+ ).hexdigest()[:8]
+ arch = self.hw_info['arch'].lower().replace(' ', '_')
+ self.miner_id = "{}-{}-{}".format(arch, self.hw_info['hostname'][:10], hw_hash)
+
+ # Generate wallet address
+ if wallet:
+ self.wallet = wallet
+ else:
+ wallet_hash = hashlib.sha256(
+ "{}-rustchain".format(self.miner_id).encode()
+ ).hexdigest()[:38]
+ family = self.hw_info['family'].lower().replace(' ', '_')
+ self.wallet = "{}_{}RTC".format(family, wallet_hash)
+
+ # Set up transport (HTTPS direct or HTTP proxy)
+ self.transport = NodeTransport(
+ node_url or NODE_URL,
+ proxy_url or PROXY_URL
+ )
+
+ self.attestation_valid_until = 0
+ self.shares_submitted = 0
+ self.shares_accepted = 0
+ self.last_entropy = {}
+ self._last_system_time = time.monotonic()
+
+ self._print_banner()
+
+ # Run initial fingerprint check
+ if FINGERPRINT_AVAILABLE:
+ self._run_fingerprint_checks()
+
+ def _run_fingerprint_checks(self):
+ """Run hardware fingerprint checks for RIP-PoA."""
+ print(info("\n[FINGERPRINT] Running hardware fingerprint checks..."))
+ try:
+ passed, results = validate_all_checks()
+ self.fingerprint_passed = passed
+ self.fingerprint_data = {"checks": results, "all_passed": passed}
+ if passed:
+ print(success("[FINGERPRINT] All checks PASSED - eligible for full rewards"))
+ else:
+ failed = [k for k, v in results.items() if not v.get("passed")]
+ print(warning("[FINGERPRINT] FAILED checks: {}".format(failed)))
+ print(warning("[FINGERPRINT] WARNING: May receive reduced/zero rewards"))
+ except Exception as e:
+ print(error("[FINGERPRINT] Error running checks: {}".format(e)))
+ self.fingerprint_passed = False
+ self.fingerprint_data = {"error": str(e), "all_passed": False}
+
+ def _print_banner(self):
+ print("=" * 70)
+ print("RustChain Mac Miner v{} - Serial Binding + Fingerprint".format(MINER_VERSION))
+ print("=" * 70)
+ print("Miner ID: {}".format(self.miner_id))
+ print("Wallet: {}".format(self.wallet))
+ print("Transport: {}".format(
+ "PROXY ({})".format(self.transport.proxy_url) if self.transport.use_proxy
+ else "DIRECT ({})".format(self.transport.node_url)
+ ))
+ print("Serial: {}".format(self.hw_info.get('serial', 'N/A')))
+ print("-" * 70)
+ print("Hardware: {} / {}".format(self.hw_info['family'], self.hw_info['arch']))
+ print("Model: {}".format(self.hw_info['model']))
+ print("CPU: {}".format(self.hw_info['cpu']))
+ print("Cores: {}".format(self.hw_info['cores']))
+ print("Memory: {} GB".format(self.hw_info['memory_gb']))
+ print("-" * 70)
+ weight = self._get_expected_weight()
+ print("Expected Weight: {}x (Proof of Antiquity)".format(weight))
+ print("=" * 70)
+
+ def _get_expected_weight(self):
+ """Calculate expected PoA weight."""
+ arch = self.hw_info['arch'].lower()
+ family = self.hw_info['family'].lower()
+
+ if family == 'powerpc':
+ if arch == 'g3': return 3.0
+ if arch == 'g4': return 2.5
+ if arch == 'g5': return 2.0
+ elif 'apple' in family or 'silicon' in family:
+ if arch in ('m1', 'm2', 'm3', 'm4', 'apple_silicon'):
+ return 1.2
+ elif family == 'x86_64':
+ if arch == 'core2': return 1.5
+ return 1.0
+
+ return 1.0
+
+ def _detect_sleep_wake(self):
+ """Detect if the machine slept (large time jump)."""
+ now = time.monotonic()
+ gap = now - self._last_system_time
+ self._last_system_time = now
+ # If more than 2x the check interval elapsed, we probably slept
+ if gap > LOTTERY_CHECK_INTERVAL * 3:
+ return True
+ return False
+
+ def attest(self):
+ """Complete hardware attestation with fingerprint."""
+ ts = datetime.now().strftime('%H:%M:%S')
+ print(info("\n[{}] Attesting hardware...".format(ts)))
+
+ try:
+ resp = self.transport.post("/attest/challenge", json={}, timeout=15)
+ if resp.status_code != 200:
+ print(error(" ERROR: Challenge failed ({})".format(resp.status_code)))
+ return False
+
+ challenge = resp.json()
+ nonce = challenge.get("nonce", "")
+ print(success(" Got challenge nonce: {}...".format(nonce[:16])))
+
+ except Exception as e:
+ print(error(" ERROR: Challenge error: {}".format(e)))
+ return False
+
+ # Collect entropy
+ entropy = collect_entropy()
+ self.last_entropy = entropy
+
+ # Re-run fingerprint checks if needed
+ if FINGERPRINT_AVAILABLE and not self.fingerprint_data:
+ self._run_fingerprint_checks()
+
+ # Build attestation payload
+ commitment = hashlib.sha256(
+ (nonce + self.wallet + json.dumps(entropy, sort_keys=True)).encode()
+ ).hexdigest()
+
+ attestation = {
+ "miner": self.wallet,
+ "miner_id": self.miner_id,
+ "nonce": nonce,
+ "report": {
+ "nonce": nonce,
+ "commitment": commitment,
+ "derived": entropy,
+ "entropy_score": entropy.get("variance_ns", 0.0)
+ },
+ "device": {
+ "family": self.hw_info["family"],
+ "arch": self.hw_info["arch"],
+ "model": self.hw_info["model"],
+ "cpu": self.hw_info["cpu"],
+ "cores": self.hw_info["cores"],
+ "memory_gb": self.hw_info["memory_gb"],
+ "serial": self.hw_info.get("serial")
+ },
+ "signals": {
+ "macs": self.hw_info.get("macs", [self.hw_info["mac"]]),
+ "hostname": self.hw_info["hostname"]
+ },
+ "fingerprint": self.fingerprint_data,
+ "miner_version": MINER_VERSION,
+ }
+
+ try:
+ resp = self.transport.post("/attest/submit", json=attestation, timeout=30)
+
+ if resp.status_code == 200:
+ result = resp.json()
+ if result.get("ok"):
+ self.attestation_valid_until = time.time() + ATTESTATION_TTL
+ print(success(" SUCCESS: Attestation accepted!"))
+ if self.fingerprint_passed:
+ print(success(" Fingerprint: PASSED"))
+ else:
+ print(warning(" Fingerprint: FAILED (reduced rewards)"))
+ return True
+ else:
+ print(warning(" WARNING: {}".format(result)))
+ return False
+ else:
+ print(error(" ERROR: HTTP {}: {}".format(resp.status_code, resp.text[:200])))
+ return False
+
+ except Exception as e:
+ print(error(" ERROR: {}".format(e)))
+ return False
+
+ def check_eligibility(self):
+ """Check lottery eligibility."""
+ try:
+ resp = self.transport.get(
+ "/lottery/eligibility",
+ params={"miner_id": self.miner_id},
+ timeout=10,
+ )
+ if resp.status_code == 200:
+ return resp.json()
+ return {"eligible": False, "reason": "HTTP {}".format(resp.status_code)}
+ except Exception as e:
+ return {"eligible": False, "reason": str(e)}
+
+ def submit_header(self, slot):
+ """Submit header for slot."""
+ try:
+ message = "slot:{}:miner:{}:ts:{}".format(slot, self.miner_id, int(time.time()))
+ message_hex = message.encode().hex()
+ sig_data = hashlib.sha512(
+ "{}{}".format(message, self.wallet).encode()
+ ).hexdigest()
+
+ header_payload = {
+ "miner_id": self.miner_id,
+ "header": {
+ "slot": slot,
+ "miner": self.miner_id,
+ "timestamp": int(time.time())
+ },
+ "message": message_hex,
+ "signature": sig_data,
+ "pubkey": self.wallet
+ }
+
+ resp = self.transport.post(
+ "/headers/ingest_signed",
+ json=header_payload,
+ timeout=15,
+ )
+
+ self.shares_submitted += 1
+
+ if resp.status_code == 200:
+ result = resp.json()
+ if result.get("ok"):
+ self.shares_accepted += 1
+ return True, result
+ return False, result
+ return False, {"error": "HTTP {}".format(resp.status_code)}
+
+ except Exception as e:
+ return False, {"error": str(e)}
+
+ def run(self):
+ """Main mining loop with sleep-wake detection."""
+ ts = datetime.now().strftime('%H:%M:%S')
+ print("\n[{}] Starting miner...".format(ts))
+
+ # Initial attestation
+ while not self.attest():
+ print(" Retrying attestation in 30 seconds...")
+ time.sleep(30)
+
+ last_slot = 0
+ status_counter = 0
+
+ while True:
+ try:
+ # Detect sleep/wake — force re-attest
+ if self._detect_sleep_wake():
+ ts = datetime.now().strftime('%H:%M:%S')
+ print("\n[{}] Sleep/wake detected - re-attesting...".format(ts))
+ self.attestation_valid_until = 0
+
+ # Re-attest if expired
+ if time.time() > self.attestation_valid_until:
+ self.attest()
+
+ # Check eligibility
+ eligibility = self.check_eligibility()
+ slot = eligibility.get("slot", 0)
+
+ if eligibility.get("eligible"):
+ ts = datetime.now().strftime('%H:%M:%S')
+ print("\n[{}] ELIGIBLE for slot {}!".format(ts, slot))
+
+ if slot != last_slot:
+ ok, result = self.submit_header(slot)
+ if ok:
+ print(" Header ACCEPTED! Slot {}".format(slot))
+ else:
+ print(" Header rejected: {}".format(result))
+ last_slot = slot
+ else:
+ reason = eligibility.get("reason", "unknown")
+ if reason == "not_attested":
+ ts = datetime.now().strftime('%H:%M:%S')
+ print("[{}] Not attested - re-attesting...".format(ts))
+ self.attest()
+
+ # Status every ~60 seconds
+ status_counter += 1
+ if status_counter >= (60 // LOTTERY_CHECK_INTERVAL):
+ ts = datetime.now().strftime('%H:%M:%S')
+ print("[{}] Slot {} | Submitted: {} | Accepted: {}".format(
+ ts, slot, self.shares_submitted, self.shares_accepted
+ ))
+ status_counter = 0
+
+ time.sleep(LOTTERY_CHECK_INTERVAL)
+
+ except KeyboardInterrupt:
+ print("\n\nShutting down miner...")
+ break
+ except Exception as e:
+ ts = datetime.now().strftime('%H:%M:%S')
+ print("[{}] Error: {}".format(ts, e))
+ time.sleep(30)
+
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description="RustChain Mac Miner v{}".format(MINER_VERSION))
+ parser.add_argument("--version", "-v", action="version",
+ version="rustchain-mac-miner {}".format(MINER_VERSION))
+ parser.add_argument("--miner-id", "-m", help="Custom miner ID")
+ parser.add_argument("--wallet", "-w", help="Custom wallet address")
+ parser.add_argument("--node", "-n", default=NODE_URL, help="Node URL (default: {})".format(NODE_URL))
+ parser.add_argument("--proxy", "-p", default=PROXY_URL,
+ help="HTTP proxy URL for legacy Macs (default: {})".format(PROXY_URL))
+ parser.add_argument("--no-proxy", action="store_true",
+ help="Disable proxy fallback (HTTPS only)")
+ args = parser.parse_args()
+
+ node = args.node
+ proxy = None if args.no_proxy else args.proxy
+
+ miner = MacMiner(
+ miner_id=args.miner_id,
+ wallet=args.wallet,
+ node_url=node,
+ proxy_url=proxy,
+ )
+ miner.run()
From 533e83c2630dc516b78c3de6677c91201dc7e402 Mon Sep 17 00:00:00 2001
From: scooter7777 <350232762@qq.com>
Date: Sun, 1 Mar 2026 00:34:15 +0800
Subject: [PATCH 11/25] fix: update HTTP links to HTTPS for security (#449)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Clean HTTP→HTTPS fixes for explorer URL and rustchain.org link
---
docs/US_REGULATORY_POSITION.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/US_REGULATORY_POSITION.md b/docs/US_REGULATORY_POSITION.md
index 2f8afa82..f453832d 100644
--- a/docs/US_REGULATORY_POSITION.md
+++ b/docs/US_REGULATORY_POSITION.md
@@ -143,4 +143,4 @@ Representative public statements:
This document represents Elyan Labs' analysis of RTC's regulatory status based on publicly available legal frameworks. It is not legal advice. For a formal legal opinion, consult a qualified securities attorney.
-**Contact**: scott@elyanlabs.ai | [rustchain.org](http://rustchain.org) | [@RustchainPOA](https://x.com/RustchainPOA)
+**Contact**: scott@elyanlabs.ai | [rustchain.org](https://rustchain.org) | [@RustchainPOA](https://x.com/RustchainPOA)
From 9dea904ca2958f8371b84031913f35f57cc7a9e6 Mon Sep 17 00:00:00 2001
From: Joshualover <43139686+Joshualover@users.noreply.github.com>
Date: Sun, 1 Mar 2026 00:34:26 +0800
Subject: [PATCH 12/25] feat: improve fingerprint test coverage with
comprehensive test suite (#448)
Comprehensive fingerprint test suite with 20+ test cases covering hardware ID uniqueness, consistency, validation, anti-emulation, evidence requirements, and clock drift
---
tests/test_fingerprint_improved.py | 397 +++++++++++++++++++++++++++++
1 file changed, 397 insertions(+)
create mode 100644 tests/test_fingerprint_improved.py
diff --git a/tests/test_fingerprint_improved.py b/tests/test_fingerprint_improved.py
new file mode 100644
index 00000000..64f7cb28
--- /dev/null
+++ b/tests/test_fingerprint_improved.py
@@ -0,0 +1,397 @@
+"""
+Test suite for hardware fingerprint validation in RustChain.
+
+This module tests the hardware fingerprinting system which ensures
+miners are running on genuine vintage hardware.
+
+Author: Atlas (AI Bounty Hunter)
+Date: 2026-02-28
+Reward: 10 RTC for first merged PR
+"""
+
+import hashlib
+import pytest
+import sys
+import os
+from pathlib import Path
+from typing import Dict, Any, Optional, Tuple
+
+# Modules are pre-loaded in conftest.py
+integrated_node = sys.modules["integrated_node"]
+_compute_hardware_id = integrated_node._compute_hardware_id
+validate_fingerprint_data = integrated_node.validate_fingerprint_data
+
+
+class TestHardwareIDUniqueness:
+ """Test that hardware IDs are unique for different inputs."""
+
+ def test_different_serial_numbers_produce_different_ids(self):
+ """Verify that different CPU serials produce different hardware IDs."""
+ device1 = {
+ "device_model": "G4",
+ "device_arch": "ppc",
+ "device_family": "7447",
+ "cores": 1,
+ "cpu_serial": "1234567890"
+ }
+ device2 = {
+ "device_model": "G4",
+ "device_arch": "ppc",
+ "device_family": "7447",
+ "cores": 1,
+ "cpu_serial": "0987654321"
+ }
+
+ id1 = _compute_hardware_id(device1, source_ip="1.1.1.1")
+ id2 = _compute_hardware_id(device2, source_ip="1.1.1.1")
+
+ assert id1 != id2, "Different serial numbers should produce different IDs"
+ assert len(id1) == 32, "Hardware ID should be 32 characters"
+
+ def test_different_core_counts_produce_different_ids(self):
+ """Verify that different core counts produce different hardware IDs."""
+ device1 = {
+ "device_model": "G5",
+ "device_arch": "ppc64",
+ "device_family": "970",
+ "cores": 1,
+ "cpu_serial": "ABC123"
+ }
+ device2 = {
+ "device_model": "G5",
+ "device_arch": "ppc64",
+ "device_family": "970",
+ "cores": 2,
+ "cpu_serial": "ABC123"
+ }
+
+ id1 = _compute_hardware_id(device1, source_ip="1.1.1.1")
+ id2 = _compute_hardware_id(device2, source_ip="1.1.1.1")
+
+ assert id1 != id2, "Different core counts should produce different IDs"
+
+ def test_different_architectures_produce_different_ids(self):
+ """Verify that different architectures produce different hardware IDs."""
+ device1 = {
+ "device_model": "G4",
+ "device_arch": "ppc",
+ "device_family": "7447",
+ "cores": 2,
+ "cpu_serial": "SERIAL1"
+ }
+ device2 = {
+ "device_model": "G5",
+ "device_arch": "ppc64",
+ "device_family": "970",
+ "cores": 2,
+ "cpu_serial": "SERIAL2"
+ }
+
+ id1 = _compute_hardware_id(device1, source_ip="1.1.1.1")
+ id2 = _compute_hardware_id(device2, source_ip="1.1.1.1")
+
+ assert id1 != id2, "Different architectures should produce different IDs"
+
+
+class TestHardwareIDConsistency:
+ """Test that hardware IDs are consistent for same inputs."""
+
+ def test_same_device_same_ip_produces_same_id(self):
+ """Verify that identical inputs with same IP produce identical IDs."""
+ device = {
+ "device_model": "G5",
+ "device_arch": "ppc64",
+ "device_family": "970",
+ "cores": 2,
+ "cpu_serial": "ABC123"
+ }
+ signals = {"macs": ["00:11:22:33:44:55"]}
+
+ id1 = _compute_hardware_id(device, signals, source_ip="2.2.2.2")
+ id2 = _compute_hardware_id(device, signals, source_ip="2.2.2.2")
+
+ assert id1 == id2, "Same device with same IP should produce same ID"
+
+ def test_same_device_different_ip_produces_different_id(self):
+ """Verify that same device with different IP produces different ID."""
+ device = {
+ "device_model": "G4",
+ "device_arch": "ppc",
+ "device_family": "7447",
+ "cores": 1,
+ "cpu_serial": "TEST123"
+ }
+ signals = {"macs": ["AA:BB:CC:DD:EE:FF"]}
+
+ id1 = _compute_hardware_id(device, signals, source_ip="192.168.1.1")
+ id2 = _compute_hardware_id(device, signals, source_ip="10.0.0.1")
+
+ assert id1 != id2, "Same device with different IP should produce different ID"
+
+
+class TestFingerprintValidation:
+ """Test fingerprint validation logic."""
+
+ def test_validate_fingerprint_data_no_data(self):
+ """Missing fingerprint payload must fail validation."""
+ passed, reason = validate_fingerprint_data(None)
+ assert passed is False, "None data should fail validation"
+ assert reason == "missing_fingerprint_data", "Error should indicate missing data"
+
+ def test_validate_fingerprint_data_empty_dict(self):
+ """Empty dictionary should fail validation."""
+ passed, reason = validate_fingerprint_data({})
+ assert passed is False, "Empty dict should fail validation"
+
+ def test_validate_fingerprint_data_valid_data(self):
+ """Valid fingerprint data should pass validation."""
+ fingerprint = {
+ "checks": {
+ "anti_emulation": {
+ "passed": True,
+ "data": {
+ "vm_indicators": [],
+ "passed": True
+ }
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint)
+ assert passed is True, "Valid fingerprint should pass"
+
+
+class TestAntiEmulationDetection:
+ """Test VM detection and anti-emulation checks."""
+
+ def test_vm_detection_with_vboxguest(self):
+ """Verify detection of VirtualBox guest indicators."""
+ fingerprint = {
+ "checks": {
+ "anti_emulation": {
+ "passed": False,
+ "data": {
+ "vm_indicators": ["vboxguest"],
+ "passed": False
+ }
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint)
+ assert passed is False, "VM detection should fail with vboxguest"
+ assert "vm_detected" in reason, "Reason should mention VM detection"
+
+ def test_vm_detection_with_no_indicators(self):
+ """Verify no false positives when no VM indicators present."""
+ fingerprint = {
+ "checks": {
+ "anti_emulation": {
+ "passed": True,
+ "data": {
+ "vm_indicators": [],
+ "passed": True
+ }
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint)
+ assert passed is True, "No VM indicators should pass validation"
+
+ def test_vm_detection_with_multiple_indicators(self):
+ """Verify detection with multiple VM indicators."""
+ fingerprint = {
+ "checks": {
+ "anti_emulation": {
+ "passed": False,
+ "data": {
+ "vm_indicators": ["vboxguest", "vmware", "parallels"],
+ "passed": False
+ }
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint)
+ assert passed is False, "Multiple VM indicators should fail"
+
+
+class TestEvidenceRequirements:
+ """Test that evidence is required for all checks."""
+
+ def test_no_evidence_fails(self):
+ """Verify rejection if no raw evidence is provided."""
+ fingerprint = {
+ "checks": {
+ "anti_emulation": {
+ "passed": True,
+ "data": {} # Missing evidence
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint)
+ assert passed is False, "Checks with no evidence should fail"
+ assert reason == "anti_emulation_no_evidence", "Error should indicate missing evidence"
+
+ def test_empty_evidence_fails(self):
+ """Verify rejection if evidence list is empty."""
+ fingerprint = {
+ "checks": {
+ "anti_emulation": {
+ "passed": True,
+ "data": {
+ "vm_indicators": [],
+ "passed": True
+ }
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint)
+ assert passed is False, "Empty evidence should fail"
+
+
+class TestClockDriftDetection:
+ """Test clock drift detection and timing validation."""
+
+ def test_timing_too_uniform_fails(self):
+ """Verify rejection of too uniform timing (clock drift check)."""
+ fingerprint = {
+ "checks": {
+ "clock_drift": {
+ "passed": True,
+ "data": {
+ "cv": 0.000001, # Too stable
+ "samples": 100
+ }
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint)
+ assert passed is False, "Too uniform timing should fail"
+ assert "timing_too_uniform" in reason, "Reason should mention timing issue"
+
+ def test_clock_drift_insufficient_samples(self):
+ """Clock drift cannot pass with extremely low sample count."""
+ fingerprint = {
+ "checks": {
+ "clock_drift": {
+ "passed": True,
+ "data": {
+ "cv": 0.02,
+ "samples": 1 # Too few samples
+ }
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint)
+ assert passed is False, "Insufficient samples should fail"
+ assert reason.startswith("clock_drift_insufficient_samples"), "Error should mention samples"
+
+ def test_valid_clock_drift_passes(self):
+ """Valid clock drift data should pass."""
+ fingerprint = {
+ "checks": {
+ "clock_drift": {
+ "passed": True,
+ "data": {
+ "cv": 0.15, # Reasonable variation
+ "samples": 50
+ }
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint)
+ assert passed is True, "Valid clock drift should pass"
+
+
+class TestVintageHardwareTiming:
+ """Test vintage hardware-specific timing requirements."""
+
+ def test_vintage_stability_too_high(self):
+ """Verify rejection of suspicious stability on vintage hardware."""
+ claimed_device = {
+ "device_arch": "G4"
+ }
+ fingerprint = {
+ "checks": {
+ "clock_drift": {
+ "passed": True,
+ "data": {
+ "cv": 0.001, # Too stable for G4
+ "samples": 100
+ }
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint, claimed_device)
+ assert passed is False, "Suspiciously stable vintage timing should fail"
+ assert "vintage_timing_too_stable" in reason, "Reason should mention vintage timing"
+
+ def test_vintage_normal_variation_passes(self):
+ """Normal variation for vintage hardware should pass."""
+ claimed_device = {
+ "device_arch": "G4"
+ }
+ fingerprint = {
+ "checks": {
+ "clock_drift": {
+ "passed": True,
+ "data": {
+ "cv": 0.05, # Normal variation
+ "samples": 100
+ }
+ }
+ }
+ }
+ passed, reason = validate_fingerprint_data(fingerprint, claimed_device)
+ assert passed is True, "Normal vintage timing should pass"
+
+
+class TestEdgeCases:
+ """Test edge cases and boundary conditions."""
+
+ def test_unicode_serial_number(self):
+ """Verify handling of Unicode serial numbers."""
+ device = {
+ "device_model": "G5",
+ "device_arch": "ppc64",
+ "device_family": "970",
+ "cores": 2,
+ "cpu_serial": "ABC123_测试"
+ }
+ id1 = _compute_hardware_id(device, source_ip="1.1.1.1")
+ id2 = _compute_hardware_id(device, source_ip="1.1.1.1")
+ assert id1 == id2, "Unicode serial should be handled consistently"
+
+ def test_empty_signals(self):
+ """Verify handling of empty signals dictionary."""
+ device = {
+ "device_model": "G4",
+ "device_arch": "ppc",
+ "device_family": "7447",
+ "cores": 1,
+ "cpu_serial": "SERIAL"
+ }
+ signals = {}
+ id1 = _compute_hardware_id(device, signals, source_ip="1.1.1.1")
+ assert len(id1) == 32, "Empty signals should still produce valid ID"
+
+ def test_multiple_mac_addresses(self):
+ """Verify handling of multiple MAC addresses."""
+ device = {
+ "device_model": "G5",
+ "device_arch": "ppc64",
+ "device_family": "970",
+ "cores": 2,
+ "cpu_serial": "MAC123"
+ }
+ signals = {
+ "macs": [
+ "00:11:22:33:44:55",
+ "AA:BB:CC:DD:EE:FF",
+ "11:22:33:44:55:66"
+ ]
+ }
+ id1 = _compute_hardware_id(device, signals, source_ip="1.1.1.1")
+ assert len(id1) == 32, "Multiple MACs should produce valid ID"
+
+
+if __name__ == "__main__":
+ pytest.main([__file__, "-v", "--tb=short"])
From 2d055a11b2a43a855bf75479256c842ecd27a273 Mon Sep 17 00:00:00 2001
From: Scott
Date: Sat, 28 Feb 2026 10:36:13 -0600
Subject: [PATCH 13/25] Migrate all user-facing URLs from raw IP to
rustchain.org domain
All miners, SDK, wallet, tools, docs, and installer code now use
https://rustchain.org instead of http://50.28.86.131:8088 or
https://50.28.86.131. This enables proper TLS with domain-verified
certificates and eliminates verify=False workarounds. Deprecated
files and infrastructure tables retain IP for reference.
85 files changed across miners/, sdk/, wallet/, tools/, docs/, node/
Co-Authored-By: Claude Opus 4.6
---
CONTRIBUTING.md | 16 +-
INSTALL.md | 20 +-
README.md | 16 +-
README.zh-CN.md | 16 +-
README_DE.md | 16 +-
README_ZH-TW.md | 16 +-
README_ZH.md | 16 +-
discord_presence_README.md | 6 +-
discord_rich_presence.py | 2 +-
docs/API.md | 14 +-
docs/CROSS_NODE_SYNC_VALIDATOR.md | 2 +-
docs/DISCORD_LEADERBOARD_BOT.md | 4 +-
docs/FAQ_TROUBLESHOOTING.md | 16 +-
...MECHANISM_SPEC_AND_FALSIFICATION_MATRIX.md | 6 +-
docs/PROTOCOL_v1.1.md | 2 +-
docs/README.md | 12 +-
docs/WALLET_USER_GUIDE.md | 6 +-
docs/WHITEPAPER.md | 2 +-
docs/api-reference.md | 44 +-
docs/api/REFERENCE.md | 4 +-
docs/api/openapi.yaml | 4 +-
docs/attestation-flow.md | 6 +-
docs/epoch-settlement.md | 8 +-
docs/index.html | 16 +-
docs/mining.html | 8 +-
docs/network-status.html | 2 +-
docs/protocol-overview.md | 8 +-
docs/wrtc.md | 2 +-
docs/zh-CN/README.md | 16 +-
install-miner.sh | 2 +-
install.sh | 2 +-
miners/README.md | 2 +-
miners/linux/rustchain_linux_miner.py | 2 +-
miners/linux/rustchain_living_museum.py | 2 +-
.../macos/intel/rustchain_mac_miner_v2.4.py | 2 +-
miners/macos/rustchain_mac_miner_v2.4.py | 2 +-
miners/power8/rustchain_power8_miner.py | 2 +-
miners/ppc/g4/rustchain_g4_poa_miner_v2.py | 914 +++++++++---------
miners/ppc/g4/rustchain_miner.c | 4 +-
miners/ppc/g4/rustchain_miner_v6.c | 4 +-
miners/ppc/g5/g5_miner.sh | 2 +-
.../ppc/rustchain_powerpc_g4_miner_v2.2.2.py | 2 +-
miners/windows/installer/README.md | 2 +-
monitoring/README.md | 2 +-
monitoring/docker-compose.yml | 2 +-
monitoring/rustchain-exporter.py | 2 +-
node/rip_node_sync.py | 2 +-
node/rustchain_blockchain_integration.py | 2 +-
node/rustchain_download_page.py | 8 +-
node/rustchain_download_server.py | 8 +-
node/rustchain_p2p_gossip.py | 2 +-
node/rustchain_p2p_init.py | 2 +-
node/rustchain_p2p_sync.py | 2 +-
node/rustchain_p2p_sync_secure.py | 2 +-
node/rustchain_v2_integrated_v2.2.1_rip200.py | 2 +-
node/server_proxy.py | 2 +-
sdk/README.md | 8 +-
sdk/TEST_RESULTS.txt | 2 +-
sdk/example.py | 2 +-
sdk/rustchain/client.py | 18 +-
sdk/test_live_api.py | 4 +-
sdk/tests/test_client_integration.py | 6 +-
sdk/tests/test_client_unit.py | 54 +-
tools/discord_leaderboard_bot.py | 2 +-
tools/earnings_calculator.html | 2 +-
tools/leaderboard.html | 8 +-
tools/node_health_monitor.py | 2 +-
tools/node_health_monitor_config.example.json | 2 +-
tools/node_sync_validator.py | 2 +-
tools/pending_ops.py | 2 +-
tools/telegram_bot/.env.example | 2 +-
tools/telegram_bot/README.md | 4 +-
tools/telegram_bot/telegram_bot.py | 2 +-
wallet-tracker/README.md | 8 +-
wallet-tracker/rtc-wallet-tracker.html | 4 +-
wallet-tracker/test_tracker.py | 4 +-
wallet/rustchain_wallet_gui.py | 2 +-
wallet/rustchain_wallet_ppc.py | 632 ++++++------
wallet/rustchain_wallet_secure.py | 2 +-
79 files changed, 1031 insertions(+), 1031 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8864c573..5f470910 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,7 +23,7 @@ Thanks for your interest in contributing to RustChain! We pay bounties in RTC to
## What Gets Merged
-- Code that works against the live node (`https://50.28.86.131`)
+- Code that works against the live node (`https://rustchain.org`)
- Tests that actually test something meaningful
- Documentation that a human can follow end-to-end
- Security fixes with proof of concept
@@ -49,19 +49,19 @@ python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# Test against live node
-curl -sk https://50.28.86.131/health
-curl -sk https://50.28.86.131/api/miners
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/health
+curl -sk https://rustchain.org/api/miners
+curl -sk https://rustchain.org/epoch
```
## Live Infrastructure
| Endpoint | URL |
|----------|-----|
-| Node Health | `https://50.28.86.131/health` |
-| Active Miners | `https://50.28.86.131/api/miners` |
-| Current Epoch | `https://50.28.86.131/epoch` |
-| Block Explorer | `https://50.28.86.131/explorer` |
+| Node Health | `https://rustchain.org/health` |
+| Active Miners | `https://rustchain.org/api/miners` |
+| Current Epoch | `https://rustchain.org/epoch` |
+| Block Explorer | `https://rustchain.org/explorer` |
| wRTC Bridge | `https://bottube.ai/bridge` |
## RTC Payout Process
diff --git a/INSTALL.md b/INSTALL.md
index b347ea27..66e86920 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -152,7 +152,7 @@ tail -f ~/.rustchain/miner.log
### Balance Check
```bash
# Note: Using -k flag because node may use self-signed SSL certificate
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME"
```
Example output:
@@ -166,17 +166,17 @@ Example output:
### Active Miners
```bash
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
```
### Node Health
```bash
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
```
### Current Epoch
```bash
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
```
## Manual Operation
@@ -304,14 +304,14 @@ cat ~/.rustchain/miner.log
**Check:**
1. Internet connection is working
-2. Node is accessible: `curl -sk https://50.28.86.131/health`
+2. Node is accessible: `curl -sk https://rustchain.org/health`
3. Firewall isn't blocking HTTPS (port 443)
### Miner not earning rewards
**Check:**
1. Miner is actually running: `systemctl --user status rustchain-miner` or `launchctl list | grep rustchain`
-2. Wallet balance: `curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME"`
+2. Wallet balance: `curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME"`
3. Miner logs for errors: `journalctl --user -u rustchain-miner -f` or `tail -f ~/.rustchain/miner.log`
4. Hardware attestation passes: Look for "fingerprint validation" messages in logs
@@ -338,7 +338,7 @@ curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-mine
- **Documentation:** https://github.com/Scottcjn/Rustchain
- **Issues:** https://github.com/Scottcjn/Rustchain/issues
-- **Explorer:** https://50.28.86.131/explorer
+- **Explorer:** https://rustchain.org/explorer
- **Bounties:** https://github.com/Scottcjn/rustchain-bounties
## Security Notes
@@ -353,17 +353,17 @@ curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-mine
To view the certificate SHA-256 fingerprint:
```bash
-openssl s_client -connect 50.28.86.131:443 < /dev/null 2>/dev/null | openssl x509 -fingerprint -sha256 -noout
+openssl s_client -connect rustchain.org:443 < /dev/null 2>/dev/null | openssl x509 -fingerprint -sha256 -noout
```
If you want to avoid using `-k`, you can save the certificate locally and pin it:
```bash
# Save the cert once (overwrite if it changes)
-openssl s_client -connect 50.28.86.131:443 < /dev/null 2>/dev/null | openssl x509 > ~/.rustchain/rustchain-cert.pem
+openssl s_client -connect rustchain.org:443 < /dev/null 2>/dev/null | openssl x509 > ~/.rustchain/rustchain-cert.pem
# Then use it instead of -k
-curl --cacert ~/.rustchain/rustchain-cert.pem "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME"
+curl --cacert ~/.rustchain/rustchain-cert.pem "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME"
```
## Contributing
diff --git a/README.md b/README.md
index 8e012cbb..e4574d4d 100644
--- a/README.md
+++ b/README.md
@@ -164,22 +164,22 @@ If an issue persists, include logs and OS details in a new issue or bounty comme
**Check your wallet balance:**
```bash
# Note: Using -sk flags because the node may use a self-signed SSL certificate
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME"
```
**List active miners:**
```bash
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
```
**Check node health:**
```bash
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
```
**Get current epoch:**
```bash
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
```
**Manage the miner service:**
@@ -310,16 +310,16 @@ This provides cryptographic proof that RustChain state existed at a specific tim
```bash
# Check network health
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
# Get current epoch
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
# List active miners
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
# Check wallet balance
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET"
# Block explorer (web browser)
open https://rustchain.org/explorer
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 58c1574c..41f95f79 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -95,22 +95,22 @@ curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-mine
**检查钱包余额:**
```bash
# 注意:使用 -sk 标志,因为节点可能使用自签名 SSL 证书
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME"
```
**列出活跃矿工:**
```bash
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
```
**检查节点健康:**
```bash
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
```
**获取当前纪元:**
```bash
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
```
**管理矿工服务:**
@@ -240,16 +240,16 @@ RustChain 纪元 → 承诺哈希 → Ergo 交易(R4 寄存器)
```bash
# 检查网络健康
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
# 获取当前纪元
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
# 列出活跃矿工
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
# 检查钱包余额
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET"
# 区块浏览器(网页浏览器)
open https://rustchain.org/explorer
diff --git a/README_DE.md b/README_DE.md
index 137eb68e..9ae3073c 100644
--- a/README_DE.md
+++ b/README_DE.md
@@ -94,22 +94,22 @@ curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-mine
**Wallet-Guthaben prüfen:**
```bash
# Hinweis: -sk Flags werden verwendet, da der Node ein selbstsigniertes SSL-Zertifikat nutzen kann
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=DEIN_WALLET_NAME"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=DEIN_WALLET_NAME"
```
**Aktive Miner auflisten:**
```bash
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
```
**Node-Health prüfen:**
```bash
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
```
**Aktuelle Epoch abrufen:**
```bash
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
```
**Miner-Service verwalten:**
@@ -225,16 +225,16 @@ Dies bietet kryptographischen Beweis, dass der RustChain-State zu einem bestimmt
```bash
# Netzwerk-Health prüfen
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
# Aktuelle Epoch abrufen
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
# Aktive Miner auflisten
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
# Wallet-Guthaben prüfen
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=DEINE_WALLET"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=DEINE_WALLET"
# Block Explorer (Web-Browser)
open https://rustchain.org/explorer
diff --git a/README_ZH-TW.md b/README_ZH-TW.md
index c3824686..97dce543 100644
--- a/README_ZH-TW.md
+++ b/README_ZH-TW.md
@@ -94,22 +94,22 @@ curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-mine
**查詢錢包餘額:**
```bash
# 注意:使用 -sk 參數是因為節點可能使用自簽 SSL 憑證
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=你的錢包名稱"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=你的錢包名稱"
```
**列出活躍礦工:**
```bash
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
```
**檢查節點健康狀態:**
```bash
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
```
**取得當前週期:**
```bash
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
```
**管理礦工服務:**
@@ -227,16 +227,16 @@ RustChain 週期 → 承諾雜湊 → Ergo 交易(R4 暫存器)
```bash
# 檢查網路健康狀態
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
# 取得當前週期
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
# 列出活躍礦工
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
# 查詢錢包餘額
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=你的錢包"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=你的錢包"
# 區塊瀏覽器(網頁)
open https://rustchain.org/explorer
diff --git a/README_ZH.md b/README_ZH.md
index 10d1038b..45d059e2 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -94,22 +94,22 @@ curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-mine
**检查钱包余额:**
```bash
# 注意:使用-sk标志是因为节点可能使用自签名SSL证书
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME"
```
**列出活跃矿工:**
```bash
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
```
**检查节点健康:**
```bash
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
```
**获取当前纪元:**
```bash
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
```
**管理矿工服务:**
@@ -227,16 +227,16 @@ RustChain纪元 → 承诺哈希 → Ergo交易(R4寄存器)
```bash
# 检查网络健康
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
# 获取当前纪元
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
# 列出活跃矿工
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
# 检查钱包余额
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET"
# 区块浏览器(Web浏览器)
open https://rustchain.org/explorer
diff --git a/discord_presence_README.md b/discord_presence_README.md
index c74352ef..47c92be2 100644
--- a/discord_presence_README.md
+++ b/discord_presence_README.md
@@ -83,7 +83,7 @@ When your miner runs, it displays your miner ID (wallet address):
List all active miners:
```bash
-curl -sk https://50.28.86.131/api/miners | jq '.[].miner'
+curl -sk https://rustchain.org/api/miners | jq '.[].miner'
```
### Option 3: From Wallet
@@ -142,14 +142,14 @@ Your miner must be:
Check your miner status:
```bash
-curl -sk https://50.28.86.131/api/miners | jq '.[] | select(.miner=="YOUR_MINER_ID")'
+curl -sk https://rustchain.org/api/miners | jq '.[] | select(.miner=="YOUR_MINER_ID")'
```
### Balance shows 0.0 or "Error getting balance"
1. Verify your miner ID is correct
2. Make sure you're using the full wallet address (including "RTC" suffix if applicable)
-3. Check network connectivity: `curl -sk https://50.28.86.131/health`
+3. Check network connectivity: `curl -sk https://rustchain.org/health`
## Advanced Usage
diff --git a/discord_rich_presence.py b/discord_rich_presence.py
index ba38d991..d87fbc54 100644
--- a/discord_rich_presence.py
+++ b/discord_rich_presence.py
@@ -24,7 +24,7 @@
from pypresence import Presence
# RustChain API endpoint (self-signed cert requires verification=False)
-RUSTCHAIN_API = "https://50.28.86.131"
+RUSTCHAIN_API = "https://rustchain.org"
# Local state file for tracking earnings
STATE_FILE = os.path.expanduser("~/.rustchain_discord_state.json")
diff --git a/docs/API.md b/docs/API.md
index 268f6b6e..1909a095 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -1,6 +1,6 @@
# RustChain API Reference
-Base URL: `https://50.28.86.131`
+Base URL: `https://rustchain.org`
All endpoints use HTTPS. Self-signed certificates require `-k` flag with curl.
@@ -14,7 +14,7 @@ Check node status and version.
**Request:**
```bash
-curl -sk https://50.28.86.131/health | jq .
+curl -sk https://rustchain.org/health | jq .
```
**Response:**
@@ -48,7 +48,7 @@ Get current epoch details.
**Request:**
```bash
-curl -sk https://50.28.86.131/epoch | jq .
+curl -sk https://rustchain.org/epoch | jq .
```
**Response:**
@@ -80,7 +80,7 @@ List all active/enrolled miners.
**Request:**
```bash
-curl -sk https://50.28.86.131/api/miners | jq .
+curl -sk https://rustchain.org/api/miners | jq .
```
**Response:**
@@ -127,7 +127,7 @@ Check RTC balance for a miner.
**Request:**
```bash
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=eafc6f14eab6d5c5362fe651e5e6c23581892a37RTC" | jq .
+curl -sk "https://rustchain.org/wallet/balance?miner_id=eafc6f14eab6d5c5362fe651e5e6c23581892a37RTC" | jq .
```
**Response:**
@@ -151,7 +151,7 @@ Transfer RTC to another wallet. Requires Ed25519 signature.
**Request:**
```bash
-curl -sk -X POST https://50.28.86.131/wallet/transfer/signed \
+curl -sk -X POST https://rustchain.org/wallet/transfer/signed \
-H "Content-Type: application/json" \
-d '{
"from": "sender_miner_id",
@@ -181,7 +181,7 @@ Submit hardware fingerprint for epoch enrollment.
**Request:**
```bash
-curl -sk -X POST https://50.28.86.131/attest/submit \
+curl -sk -X POST https://rustchain.org/attest/submit \
-H "Content-Type: application/json" \
-d '{
"miner_id": "your_miner_id",
diff --git a/docs/CROSS_NODE_SYNC_VALIDATOR.md b/docs/CROSS_NODE_SYNC_VALIDATOR.md
index e715327f..455330df 100644
--- a/docs/CROSS_NODE_SYNC_VALIDATOR.md
+++ b/docs/CROSS_NODE_SYNC_VALIDATOR.md
@@ -18,7 +18,7 @@ This tool validates RustChain consistency across multiple nodes and reports disc
```bash
python3 tools/node_sync_validator.py \
- --nodes https://50.28.86.131 https://50.28.86.153 http://76.8.228.245:8099 \
+ --nodes https://rustchain.org https://50.28.86.153 http://76.8.228.245:8099 \
--output-json /tmp/node_sync_report.json \
--output-text /tmp/node_sync_report.txt
```
diff --git a/docs/DISCORD_LEADERBOARD_BOT.md b/docs/DISCORD_LEADERBOARD_BOT.md
index bfe308dd..1b387c1c 100644
--- a/docs/DISCORD_LEADERBOARD_BOT.md
+++ b/docs/DISCORD_LEADERBOARD_BOT.md
@@ -16,7 +16,7 @@ This script posts a RustChain leaderboard message to a Discord webhook.
```bash
python3 tools/discord_leaderboard_bot.py \
- --node https://50.28.86.131 \
+ --node https://rustchain.org \
--webhook-url "https://discord.com/api/webhooks/xxx/yyy"
```
@@ -24,7 +24,7 @@ If you prefer env vars:
```bash
export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/xxx/yyy"
-python3 tools/discord_leaderboard_bot.py --node https://50.28.86.131
+python3 tools/discord_leaderboard_bot.py --node https://rustchain.org
```
## Dry Run
diff --git a/docs/FAQ_TROUBLESHOOTING.md b/docs/FAQ_TROUBLESHOOTING.md
index 0c259d33..64d6a278 100644
--- a/docs/FAQ_TROUBLESHOOTING.md
+++ b/docs/FAQ_TROUBLESHOOTING.md
@@ -14,7 +14,7 @@ This guide covers common setup and runtime issues for miners and node users.
### 2) How do I check if the network is online?
```bash
-curl -sk https://50.28.86.131/health | jq .
+curl -sk https://rustchain.org/health | jq .
```
You should see a JSON response. If the command times out repeatedly, check local firewall/VPN and retry.
@@ -22,7 +22,7 @@ You should see a JSON response. If the command times out repeatedly, check local
### 3) How do I verify my miner is visible?
```bash
-curl -sk https://50.28.86.131/api/miners | jq .
+curl -sk https://rustchain.org/api/miners | jq .
```
If your miner is missing, wait a few minutes after startup and re-check logs.
@@ -30,7 +30,7 @@ If your miner is missing, wait a few minutes after startup and re-check logs.
### 4) How do I check wallet balance?
```bash
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME" | jq .
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME" | jq .
```
### 5) Is self-signed TLS expected on the node API?
@@ -38,7 +38,7 @@ curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME" | jq .
Yes. Existing docs use `-k`/`--insecure` for this reason:
```bash
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
```
## Troubleshooting
@@ -68,9 +68,9 @@ Checks:
Commands:
```bash
-curl -sk https://50.28.86.131/health | jq .
-curl -sk https://50.28.86.131/api/miners | jq .
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME" | jq .
+curl -sk https://rustchain.org/health | jq .
+curl -sk https://rustchain.org/api/miners | jq .
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME" | jq .
```
### API calls fail with SSL/certificate errors
@@ -78,7 +78,7 @@ curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME" | jq .
Use `-k` as shown in official docs:
```bash
-curl -sk https://50.28.86.131/api/miners | jq .
+curl -sk https://rustchain.org/api/miners | jq .
```
### Bridge/swap confusion (RTC vs wRTC)
diff --git a/docs/MECHANISM_SPEC_AND_FALSIFICATION_MATRIX.md b/docs/MECHANISM_SPEC_AND_FALSIFICATION_MATRIX.md
index 46880c43..685f4a07 100644
--- a/docs/MECHANISM_SPEC_AND_FALSIFICATION_MATRIX.md
+++ b/docs/MECHANISM_SPEC_AND_FALSIFICATION_MATRIX.md
@@ -35,9 +35,9 @@ If any "Fail condition" occurs, the corresponding claim is falsified.
| Claim | Mechanism Under Test | How to Test | Pass Condition | Fail Condition |
|---|---|---|---|---|
-| C1: Node health/status is deterministic and machine-readable | Health endpoint | `curl -sk https://50.28.86.131/health \| jq .` | JSON response with `ok=true`, `version`, and runtime fields | Endpoint missing, malformed, or non-deterministic health state |
-| C2: Epoch state is explicit and observable | Epoch endpoint | `curl -sk https://50.28.86.131/epoch \| jq .` | Returns epoch/slot/pot fields and advances over time | No epoch data or inconsistent epoch progression |
-| C3: Miner enrollment + multipliers are transparent | Miner list endpoint | `curl -sk https://50.28.86.131/api/miners \| jq .` | Active miners listed with hardware fields and `antiquity_multiplier` | Missing/opaque miner state or absent multiplier disclosure |
+| C1: Node health/status is deterministic and machine-readable | Health endpoint | `curl -sk https://rustchain.org/health \| jq .` | JSON response with `ok=true`, `version`, and runtime fields | Endpoint missing, malformed, or non-deterministic health state |
+| C2: Epoch state is explicit and observable | Epoch endpoint | `curl -sk https://rustchain.org/epoch \| jq .` | Returns epoch/slot/pot fields and advances over time | No epoch data or inconsistent epoch progression |
+| C3: Miner enrollment + multipliers are transparent | Miner list endpoint | `curl -sk https://rustchain.org/api/miners \| jq .` | Active miners listed with hardware fields and `antiquity_multiplier` | Missing/opaque miner state or absent multiplier disclosure |
| C4: Signed transfer replay is blocked | Nonce replay protection | Send the same signed payload (same nonce/signature) to `/wallet/transfer/signed` twice | First request accepted; second request rejected as replay/duplicate | Same signed payload executes twice |
| C5: Signature checks are enforced | Signature verification | Submit intentionally invalid signature to `/wallet/transfer/signed` | Transfer rejected with validation error | Invalid signature accepted and state mutates |
| C6: Cross-node reads can be compared for drift | API consistency | Compare `/health`, `/epoch`, `/api/miners` across live nodes (131, 153, 245) | Differences stay within expected propagation window and reconcile | Persistent divergence with no reconciliation |
diff --git a/docs/PROTOCOL_v1.1.md b/docs/PROTOCOL_v1.1.md
index ec99d0d5..5dd5281a 100644
--- a/docs/PROTOCOL_v1.1.md
+++ b/docs/PROTOCOL_v1.1.md
@@ -50,7 +50,7 @@ Older hardware is weighted heavier to incentivize preservation.
## 5. Network Architecture
### 5.1 Nodes
The network relies on trusted **Attestation Nodes** to validate fingerprints.
-* **Primary Node**: `https://50.28.86.131`
+* **Primary Node**: `https://rustchain.org`
* **Ergo Anchor Node**: `https://50.28.86.153`
### 5.2 Ergo Anchoring
diff --git a/docs/README.md b/docs/README.md
index ce97ac38..9ccace29 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -21,22 +21,22 @@
## Live Network
-- **Primary Node**: `https://50.28.86.131`
-- **Explorer**: `https://50.28.86.131/explorer`
-- **Health Check**: `curl -sk https://50.28.86.131/health`
+- **Primary Node**: `https://rustchain.org`
+- **Explorer**: `https://rustchain.org/explorer`
+- **Health Check**: `curl -sk https://rustchain.org/health`
- **Network Status Page**: `docs/network-status.html` (GitHub Pages-hostable status dashboard)
## Current Stats
```bash
# Check node health
-curl -sk https://50.28.86.131/health | jq .
+curl -sk https://rustchain.org/health | jq .
# List active miners
-curl -sk https://50.28.86.131/api/miners | jq .
+curl -sk https://rustchain.org/api/miners | jq .
# Current epoch info
-curl -sk https://50.28.86.131/epoch | jq .
+curl -sk https://rustchain.org/epoch | jq .
```
## Architecture Overview
diff --git a/docs/WALLET_USER_GUIDE.md b/docs/WALLET_USER_GUIDE.md
index e80bd3f0..f72e3c6d 100644
--- a/docs/WALLET_USER_GUIDE.md
+++ b/docs/WALLET_USER_GUIDE.md
@@ -10,7 +10,7 @@ This guide explains wallet basics, balance checks, and safe transfer practices f
## 2) Check wallet balance
```bash
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET_NAME" | jq .
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME" | jq .
```
Expected response shape:
@@ -26,7 +26,7 @@ Expected response shape:
## 3) Confirm miner is active
```bash
-curl -sk https://50.28.86.131/api/miners | jq .
+curl -sk https://rustchain.org/api/miners | jq .
```
If your miner does not appear:
@@ -63,7 +63,7 @@ Only use this when you fully understand signing and key custody.
Current docs use `curl -k` for self-signed TLS:
```bash
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
```
### Wrong chain/token confusion (RTC vs wRTC)
diff --git a/docs/WHITEPAPER.md b/docs/WHITEPAPER.md
index f87e86e1..7b5b4203 100644
--- a/docs/WHITEPAPER.md
+++ b/docs/WHITEPAPER.md
@@ -805,7 +805,7 @@ The Proof-of-Antiquity mechanism proves that blockchain can align economic incen
1. RustChain GitHub Repository: https://github.com/Scottcjn/Rustchain
2. Bounties Repository: https://github.com/Scottcjn/rustchain-bounties
-3. Live Explorer: https://50.28.86.131/explorer
+3. Live Explorer: https://rustchain.org/explorer
### Technical Standards
diff --git a/docs/api-reference.md b/docs/api-reference.md
index 1d2698da..2016e254 100644
--- a/docs/api-reference.md
+++ b/docs/api-reference.md
@@ -4,7 +4,7 @@
RustChain provides a REST API for interacting with the network. All endpoints use HTTPS with a self-signed certificate (use `-k` flag with curl).
-**Base URL**: `https://50.28.86.131`
+**Base URL**: `https://rustchain.org`
**Internal URL**: `http://localhost:8099` (on VPS only)
@@ -25,7 +25,7 @@ Most endpoints are public. Admin endpoints require the `X-Admin-Key` header:
Check node health status.
```bash
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
```
**Response**:
@@ -56,7 +56,7 @@ curl -sk https://50.28.86.131/health
Kubernetes-style readiness probe.
```bash
-curl -sk https://50.28.86.131/ready
+curl -sk https://rustchain.org/ready
```
**Response**:
@@ -75,7 +75,7 @@ curl -sk https://50.28.86.131/ready
Get current epoch and slot information.
```bash
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
```
**Response**:
@@ -106,7 +106,7 @@ curl -sk https://50.28.86.131/epoch
List all active miners with hardware details.
```bash
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
```
**Response**:
@@ -153,7 +153,7 @@ curl -sk https://50.28.86.131/api/miners
List connected attestation nodes.
```bash
-curl -sk https://50.28.86.131/api/nodes
+curl -sk https://rustchain.org/api/nodes
```
**Response**:
@@ -185,7 +185,7 @@ curl -sk https://50.28.86.131/api/nodes
Check RTC balance for a miner wallet.
```bash
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=scott"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=scott"
```
**Parameters**:
@@ -220,7 +220,7 @@ curl -sk "https://50.28.86.131/wallet/balance?miner_id=scott"
Submit hardware attestation to enroll in current epoch.
```bash
-curl -sk -X POST https://50.28.86.131/attest/submit \
+curl -sk -X POST https://rustchain.org/attest/submit \
-H "Content-Type: application/json" \
-d '{
"miner_id": "scott",
@@ -276,7 +276,7 @@ curl -sk -X POST https://50.28.86.131/attest/submit \
Check if miner is enrolled in current epoch.
```bash
-curl -sk "https://50.28.86.131/lottery/eligibility?miner_id=scott"
+curl -sk "https://rustchain.org/lottery/eligibility?miner_id=scott"
```
**Response**:
@@ -299,7 +299,7 @@ curl -sk "https://50.28.86.131/lottery/eligibility?miner_id=scott"
Web UI for browsing blocks and transactions.
```bash
-open https://50.28.86.131/explorer
+open https://rustchain.org/explorer
```
Returns HTML page (not JSON).
@@ -313,7 +313,7 @@ Returns HTML page (not JSON).
Query historical settlement data for a specific epoch.
```bash
-curl -sk https://50.28.86.131/api/settlement/75
+curl -sk https://rustchain.org/api/settlement/75
```
**Response**:
@@ -347,7 +347,7 @@ These endpoints require the `X-Admin-Key` header.
Transfer RTC between wallets (admin only).
```bash
-curl -sk -X POST https://50.28.86.131/wallet/transfer \
+curl -sk -X POST https://rustchain.org/wallet/transfer \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
@@ -375,7 +375,7 @@ curl -sk -X POST https://50.28.86.131/wallet/transfer \
Manually trigger epoch settlement (admin only).
```bash
-curl -sk -X POST https://50.28.86.131/rewards/settle \
+curl -sk -X POST https://rustchain.org/rewards/settle \
-H "X-Admin-Key: YOUR_ADMIN_KEY"
```
@@ -401,7 +401,7 @@ These endpoints support the x402 payment protocol (currently free during beta).
Bulk video export (BoTTube integration).
```bash
-curl -sk https://50.28.86.131/api/premium/videos
+curl -sk https://rustchain.org/api/premium/videos
```
---
@@ -411,7 +411,7 @@ curl -sk https://50.28.86.131/api/premium/videos
Deep agent analytics.
```bash
-curl -sk https://50.28.86.131/api/premium/analytics/scott
+curl -sk https://rustchain.org/api/premium/analytics/scott
```
---
@@ -421,7 +421,7 @@ curl -sk https://50.28.86.131/api/premium/analytics/scott
USDC/wRTC swap guidance.
```bash
-curl -sk https://50.28.86.131/wallet/swap-info
+curl -sk https://rustchain.org/wallet/swap-info
```
**Response**:
@@ -494,12 +494,12 @@ The node uses a self-signed certificate. Options:
```bash
# Option 1: Skip verification (development)
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
# Option 2: Download and trust certificate
-openssl s_client -connect 50.28.86.131:443 -showcerts < /dev/null 2>/dev/null | \
+openssl s_client -connect rustchain.org:443 -showcerts < /dev/null 2>/dev/null | \
openssl x509 -outform PEM > rustchain.pem
-curl --cacert rustchain.pem https://50.28.86.131/health
+curl --cacert rustchain.pem https://rustchain.org/health
```
---
@@ -511,7 +511,7 @@ curl --cacert rustchain.pem https://50.28.86.131/health
```python
import requests
-BASE_URL = "https://50.28.86.131"
+BASE_URL = "https://rustchain.org"
def get_balance(miner_id):
resp = requests.get(
@@ -533,7 +533,7 @@ print(get_epoch())
### JavaScript
```javascript
-const BASE_URL = "https://50.28.86.131";
+const BASE_URL = "https://rustchain.org";
async function getBalance(minerId) {
const resp = await fetch(
@@ -556,7 +556,7 @@ getEpoch().then(console.log);
```bash
#!/bin/bash
-BASE_URL="https://50.28.86.131"
+BASE_URL="https://rustchain.org"
# Get balance
get_balance() {
diff --git a/docs/api/REFERENCE.md b/docs/api/REFERENCE.md
index 60ec1303..cad8013d 100644
--- a/docs/api/REFERENCE.md
+++ b/docs/api/REFERENCE.md
@@ -1,6 +1,6 @@
# RustChain API Reference
-**Base URL:** `https://50.28.86.131` (Primary Node)
+**Base URL:** `https://rustchain.org` (Primary Node)
**Authentication:** Read-only endpoints are public. Writes require Ed25519 signatures or an Admin Key.
**Certificate Note:** The node uses a self-signed TLS certificate. Use the `-k` flag with `curl` or disable certificate verification in your client.
@@ -67,7 +67,7 @@ List all miners currently participating in the network with their hardware detai
Query the RTC balance for any valid miner ID.
- **Endpoint:** `GET /wallet/balance?miner_id={NAME}`
-- **Example:** `curl -sk 'https://50.28.86.131/wallet/balance?miner_id=scott'`
+- **Example:** `curl -sk 'https://rustchain.org/wallet/balance?miner_id=scott'`
- **Response:**
```json
{
diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml
index b363d98f..57206245 100644
--- a/docs/api/openapi.yaml
+++ b/docs/api/openapi.yaml
@@ -13,7 +13,7 @@ info:
Write operations (transfers) require cryptographic signatures.
## Base URL
- Production: `https://50.28.86.131`
+ Production: `https://rustchain.org`
**Note:** The server uses a self-signed TLS certificate.
version: 2.2.1
@@ -25,7 +25,7 @@ info:
url: https://opensource.org/licenses/MIT
servers:
- - url: https://50.28.86.131
+ - url: https://rustchain.org
description: RustChain Mainnet Node
tags:
diff --git a/docs/attestation-flow.md b/docs/attestation-flow.md
index 73e56068..016e0f9e 100644
--- a/docs/attestation-flow.md
+++ b/docs/attestation-flow.md
@@ -165,7 +165,7 @@ signature = signing_key.sign(message)
payload["signature"] = base64.b64encode(signature).decode('ascii')
# Submit
-requests.post("https://50.28.86.131/attest/submit", json=payload)
+requests.post("https://rustchain.org/attest/submit", json=payload)
```
## What Nodes Validate
@@ -406,7 +406,7 @@ Submit hardware attestation.
**Request**:
```bash
-curl -sk -X POST https://50.28.86.131/attest/submit \
+curl -sk -X POST https://rustchain.org/attest/submit \
-H "Content-Type: application/json" \
-d @attestation.json
```
@@ -437,7 +437,7 @@ Check if miner is enrolled in current epoch.
**Request**:
```bash
-curl -sk "https://50.28.86.131/lottery/eligibility?miner_id=scott"
+curl -sk "https://rustchain.org/lottery/eligibility?miner_id=scott"
```
**Response**:
diff --git a/docs/epoch-settlement.md b/docs/epoch-settlement.md
index 66e27840..b98367de 100644
--- a/docs/epoch-settlement.md
+++ b/docs/epoch-settlement.md
@@ -347,7 +347,7 @@ Get current epoch information.
**Request**:
```bash
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
```
**Response**:
@@ -368,7 +368,7 @@ Check wallet balance after settlement.
**Request**:
```bash
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=scott"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=scott"
```
**Response**:
@@ -390,7 +390,7 @@ Query historical settlement data.
**Request**:
```bash
-curl -sk https://50.28.86.131/api/settlement/75
+curl -sk https://rustchain.org/api/settlement/75
```
**Response**:
@@ -450,7 +450,7 @@ tail -f /var/log/rustchain/node.log | grep SETTLEMENT
```bash
# Check if settlement completed
-curl -sk https://50.28.86.131/api/settlement/75 | jq '.ergo_tx_id'
+curl -sk https://rustchain.org/api/settlement/75 | jq '.ergo_tx_id'
# Verify on Ergo explorer
curl "https://api.ergoplatform.com/api/v1/transactions/abc123..."
diff --git a/docs/index.html b/docs/index.html
index 2161d954..d16ff5a4 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -420,13 +420,13 @@ Start Mining
# Check the network is alive
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
# See active miners
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
# Check your balance after mining
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET"
Current Mining Fleet
@@ -472,10 +472,10 @@ Attestation Nodes
@@ -560,7 +560,7 @@ Sign the Guestbook
Quick Links
- Live Block Explorer
+ Live Block Explorer
Live BoTTube.ai — AI video platform
Live Bounty Board
GitHub RustChain repo
diff --git a/docs/mining.html b/docs/mining.html
index 0fdff1f0..437bc66b 100644
--- a/docs/mining.html
+++ b/docs/mining.html
@@ -338,16 +338,16 @@
Monitoring Your Mining
RustChain provides several tools to monitor your mining activity:
# Check your balance
-curl -sk "https://50.28.86.131/wallet/balance?miner_id=YOUR_WALLET"
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET"
# View active miners
-curl -sk https://50.28.86.131/api/miners
+curl -sk https://rustchain.org/api/miners
# Check current epoch
-curl -sk https://50.28.86.131/epoch
+curl -sk https://rustchain.org/epoch
# Network health check
-curl -sk https://50.28.86.131/health
+curl -sk https://rustchain.org/health
Withdrawing Rewards
Once you've accumulated sufficient RTC, you can withdraw to external wallets or trade on supported exchanges. The RustChain light client provides an easy-to-use interface for managing your wallet and transactions.
diff --git a/docs/network-status.html b/docs/network-status.html
index 574b9f80..41263803 100644
--- a/docs/network-status.html
+++ b/docs/network-status.html
@@ -35,7 +35,7 @@
Response Time (recent)
+