Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
645 changes: 207 additions & 438 deletions docs/api/openapi.yaml

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions monitoring/rustchain-prometheus-exporter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# RustChain Prometheus Metrics Exporter

A Prometheus-compatible metrics exporter for RustChain node monitoring.

## Installation

```bash
pip install git+https://github.com/sososonia-cyber/RustChain.git
```

Or run directly:

```bash
python rustchain_exporter.py
```

## Usage

### Run exporter

```bash
python rustchain_exporter.py --port 9090 --node-url https://50.28.86.131
```

### Add to Prometheus

Add to your `prometheus.yml`:

```yaml
scrape_configs:
- job_name: 'rustchain'
static_configs:
- targets: ['localhost:9090']
```

## Metrics

| Metric | Type | Description |
|--------|------|-------------|
| `rustchain_node_up` | gauge | Node operational status |
| `rustchain_node_uptime_seconds` | counter | Node uptime |
| `rustchain_active_miners_total` | gauge | Number of active miners |
| `rustchain_current_epoch` | gauge | Current epoch number |
| `rustchain_current_slot` | gauge | Current slot |
| `rustchain_epoch_slot_progress` | gauge | Epoch progress (0-1) |
| `rustchain_miner_info` | gauge | Miner details |
| `rustchain_miner_antiquity_multiplier` | gauge | Miner antiquity multiplier |

## Docker

```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY rustchain_exporter.py .
RUN pip install prometheus-client
EXPOSE 9090
CMD ["python", "rustchain_exporter.py"]
```

## License

MIT

## Author

Built by Atlas (AI Agent) for RustChain Bounty #504
200 changes: 200 additions & 0 deletions monitoring/rustchain_exporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#!/usr/bin/env python3
"""
RustChain Prometheus Metrics Exporter

Exposes RustChain node metrics for Prometheus monitoring.

Usage:
python rustchain_exporter.py [--port PORT] [--node-url URL]

Metrics:
- Node health and uptime
- Active/enrolled miners
- Current epoch and slot
- Balance information
- Hall of Fame statistics
"""

import argparse
import time
import json
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.request import urlopen, Request
from urllib.error import URLError
import ssl
import threading
from typing import Dict, Any, Optional


DEFAULT_NODE_URL = "https://50.28.86.131"
DEFAULT_PORT = 9090


class PrometheusExporter:
"""RustChain metrics exporter"""

def __init__(self, node_url: str = DEFAULT_NODE_URL, verify_ssl: bool = False):
self.node_url = node_url.rstrip("/")
self.verify_ssl = verify_ssl

if not verify_ssl:
self.ctx = ssl.create_default_context()
self.ctx.check_hostname = False
self.ctx.verify_mode = ssl.CERT_NONE
else:
self.ctx = None

def _request(self, endpoint: str) -> Optional[Dict]:
"""Make API request to node"""
url = f"{self.node_url}{endpoint}"
try:
req = Request(url, headers={'Accept': 'application/json'})
with urlopen(req, context=self.ctx, timeout=10) as response:
return json.loads(response.read().decode('utf-8'))
except Exception as e:
print(f"Error fetching {endpoint}: {e}")
return None

def collect(self) -> str:
"""Collect all metrics and return Prometheus format"""
metrics = []

# Node health
health = self._request("/health")
if health:
metrics.extend([
f'rustchain_node_up{{version="{health.get("version", "unknown")}"}} 1',
f"rustchain_node_uptime_seconds {health.get('uptime_s', 0)}",
f"rustchain_node_db_rw {1 if health.get('db_rw') else 0}",
f"rustchain_node_backup_age_hours {health.get('backup_age_hours', 0)}",
])

# Miners
miners = self._request("/api/miners")
if miners:
active_count = len(miners)
metrics.append(f"rustchain_active_miners_total {active_count}")

# Per-miner metrics
for miner in miners:
name = miner.get('miner', 'unknown')
# Escape label values
name = name.replace('"', '\\"').replace('\\', '\\\\')
arch = miner.get('device_arch', 'unknown').replace('"', '\\"')
family = miner.get('device_family', 'unknown').replace('"', '\\"')
hw_type = miner.get('hardware_type', 'unknown').replace('"', '\\"')
mult = miner.get('antiquity_multiplier', 0)

metrics.append(
f'rustchain_miner_info{{miner="{name}",arch="{arch}",family="{family}",hardware_type="{hw_type}"}} 1'
)
metrics.append(
f'rustchain_miner_antiquity_multiplier{{miner="{name}"}} {mult}'
)
if miner.get('last_attest'):
metrics.append(
f'rustchain_miner_last_attest_timestamp{{miner="{name}"}} {miner["last_attest"]}'
)

# Epoch info
epoch = self._request("/epoch")
if epoch:
metrics.extend([
f"rustchain_current_epoch {epoch.get('epoch', 0)}",
f"rustchain_blocks_per_epoch {epoch.get('blocks_per_epoch', 0)}",
f"rustchain_current_slot {epoch.get('slot', 0)}",
f"rustchain_epoch_pot {epoch.get('epoch_pot', 0)}",
f"rustchain_enrolled_miners_total {epoch.get('enrolled_miners', 0)}",
f"rustchain_total_supply_rtc {epoch.get('total_supply_rtc', 0)}",
])

# Calculate slot progress
slot = epoch.get('slot', 0)
blocks = epoch.get('blocks_per_epoch', 1)
progress = slot / blocks if blocks > 0 else 0
metrics.append(f"rustchain_epoch_slot_progress {progress}")

# Build output
output = ["# HELP rustchain_node_up Node operational status"]
output.append("# TYPE rustchain_node_up gauge")
output.append("# HELP rustchain_node_uptime_seconds Node uptime in seconds")
output.append("# TYPE rustchain_node_uptime_seconds counter")
output.append("# HELP rustchain_active_miners_total Number of active miners")
output.append("# TYPE rustchain_active_miners_total gauge")
output.append("# HELP rustchain_current_epoch Current epoch number")
output.append("# TYPE rustchain_current_epoch gauge")
output.append("# HELP rustchain_miner_info Miner information")
output.append("# TYPE rustchain_miner_info gauge")
output.append("# HELP rustchain_miner_antiquity_multiplier Miner antiquity multiplier")
output.append("# TYPE rustchain_miner_antiquity_multiplier gauge")

output.extend(metrics)
output.append("") # Empty line at end

return "\n".join(output)


class PrometheusHandler(BaseHTTPRequestHandler):
"""HTTP handler for Prometheus scraping"""

exporter: Optional[PrometheusExporter] = None

def do_GET(self):
if self.path == "/metrics" or self.path == "/":
if self.exporter:
metrics = self.exporter.collect()
self.send_response(200)
self.send_header("Content-Type", "text/plain; version=0.0.4")
self.end_headers()
self.wfile.write(metrics.encode('utf-8'))
else:
self.send_response(500)
self.end_headers()
elif self.path == "/health":
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b'{"status": "ok"}')
else:
self.send_response(404)
self.end_headers()

def log_message(self, format, *args):
# Suppress logging
pass


def run_exporter(port: int = DEFAULT_PORT, node_url: str = DEFAULT_NODE_URL):
"""Run the exporter"""
exporter = PrometheusExporter(node_url)
PrometheusHandler.exporter = exporter

server = HTTPServer(("0.0.0.0", port), PrometheusHandler)
print(f"RustChain Prometheus exporter running on port {port}")
print(f"Node URL: {node_url}")
print(f"Metrics endpoint: http://localhost:{port}/metrics")

try:
server.serve_forever()
except KeyboardInterrupt:
print("\nShutting down...")
server.shutdown()


def main():
parser = argparse.ArgumentParser(description="RustChain Prometheus Metrics Exporter")
parser.add_argument("--port", type=int, default=DEFAULT_PORT, help="Port to listen on")
parser.add_argument("--node-url", default=DEFAULT_NODE_URL, help="RustChain node URL")
parser.add_argument("--verify-ssl", action="store_true", help="Verify SSL certificates")

args = parser.parse_args()

# Override verify SSL if flag provided
exporter = PrometheusExporter(args.node_url, args.verify_ssl)
PrometheusHandler.exporter = exporter

run_exporter(args.port, args.node_url)


if __name__ == "__main__":
main()
128 changes: 128 additions & 0 deletions sdk/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# RustChain Python SDK

A pip-installable Python SDK for interacting with the RustChain blockchain network.

## What is RustChain?

RustChain is a Proof-of-Antiquity blockchain that rewards vintage hardware (PowerPC G4/G5, 68K Macs, SPARC, etc.) with higher mining multipliers than modern machines.

## Installation

### From GitHub (Recommended for development)
```bash
pip install git+https://github.com/sososonia-cyber/RustChain.git
```

### From PyPI (coming soon)
```bash
pip install rustchain-sdk
```

### For async support
```bash
pip install rustchain-sdk[async]
```

## Quick Start

```python
from rustchain_sdk import RustChainClient

# Create client (self-signed SSL certs handled automatically)
client = RustChainClient("https://50.28.86.131")

# Check node health
health = client.health()
print(f"Node OK: {health['ok']}")
print(f"Version: {health['version']}")

# Get active miners
miners = client.get_miners()
print(f"Active miners: {len(miners)}")

# Check epoch info
epoch = client.get_epoch()
print(f"Current epoch: {epoch['epoch']}")

# Check lottery eligibility
eligibility = client.check_eligibility("my-wallet")
print(f"Eligible: {eligibility['eligible']}")
```

## API Reference

### Client Configuration

```python
# Default configuration
client = RustChainClient()

# Custom configuration
client = RustChainClient(
base_url="https://50.28.86.131", # Node URL
verify_ssl=False, # Set True to verify SSL (for production)
timeout=30, # Request timeout in seconds
retry_count=3, # Number of retries on failure
retry_delay=1.0 # Delay between retries
)
```

### Available Methods

| Method | Description |
|--------|-------------|
| `client.health()` | Get node health status |
| `client.get_miners()` | Get list of active miners |
| `client.get_balance(miner_id)` | Get wallet balance |
| `client.get_epoch()` | Get current epoch info |
| `client.check_eligibility(miner_id)` | Check lottery eligibility |
| `client.submit_attestation(payload)` | Submit attestation |
| `client.transfer(from, to, amount, private_key)` | Transfer RTC |

### Async Support

```python
import asyncio
from rustchain_sdk import RustChainClient

async def main():
client = RustChainClient()

# Use async methods
health = await client.async_health()
miners = await client.async_get_miners()

print(f"Miners: {len(miners)}")

asyncio.run(main())
```

## Command Line Interface

```bash
# Check node health
rustchain-cli health

# List miners
rustchain-cli miners

# Check balance
rustchain-cli balance my-wallet

# Check epoch
rustchain-cli epoch
```

## Requirements

- Python 3.8+
- requests >= 2.28.0
- aiohttp >= 3.8.0 (optional, for async support)

## License

MIT License

## Author

Built by Atlas (AI Agent) for RustChain Bounty #36
Loading