Skip to content
Open
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
4 changes: 4 additions & 0 deletions node/airdrop_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,10 @@ def claim_airdrop():
@app.route("/api/airdrop/claim/<claim_id>", methods=["GET"])
def get_airdrop_claim(claim_id: str):
"""Get claim status."""
# SECURITY: Require admin key — exposes github_username, wallet_address, and airdrop tier
auth_err = require_admin_key()
if auth_err:
return auth_err
claim = airdrop.get_claim(claim_id)
if claim:
return jsonify({"ok": True, "claim": claim.to_dict()})
Expand Down
43 changes: 43 additions & 0 deletions node/beacon_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""
import json
import html
import hmac
import math
import os
import time
Expand Down Expand Up @@ -277,6 +278,13 @@ def _authenticate_contract_agent(db, allowed_agents, body_bytes):
@beacon_api.route('/api/agents', methods=['GET'])
def get_agents():
"""Get all registered agents."""
# SECURITY: Require admin key — exposes all relay agents with pubkeys, coinbase addresses, status
admin_key = os.environ.get("RC_ADMIN_KEY", "")
if not admin_key:
return jsonify({'error': 'RC_ADMIN_KEY not configured'}), 503
provided_key = request.headers.get("X-Admin-Key", "")
if not hmac.compare_digest(provided_key, admin_key):
return jsonify({'error': 'Unauthorized'}), 401
try:
db = get_db()
rows = db.execute(
Expand All @@ -302,6 +310,13 @@ def get_agents():
@beacon_api.route('/api/agent/<agent_id>', methods=['GET'])
def get_agent(agent_id):
"""Get single agent details."""
# SECURITY: Require admin key — exposes agent pubkey, coinbase address, status
admin_key = os.environ.get("RC_ADMIN_KEY", "")
if not admin_key:
return jsonify({'error': 'RC_ADMIN_KEY not configured'}), 503
provided_key = request.headers.get("X-Admin-Key", "")
if not hmac.compare_digest(provided_key, admin_key):
return jsonify({'error': 'Unauthorized'}), 401
try:
db = get_db()
row = db.execute(
Expand Down Expand Up @@ -537,6 +552,13 @@ def beacon_atlas():
@beacon_api.route('/api/contracts', methods=['GET'])
def get_contracts():
"""Get all active contracts."""
# SECURITY: Require admin key — exposes all beacon contracts, agent IDs, contract terms
admin_key = os.environ.get("RC_ADMIN_KEY", "")
if not admin_key:
return jsonify({'error': 'RC_ADMIN_KEY not configured'}), 503
provided_key = request.headers.get("X-Admin-Key", "")
if not hmac.compare_digest(provided_key, admin_key):
return jsonify({'error': 'Unauthorized'}), 401
try:
db = get_db()
rows = db.execute(
Expand Down Expand Up @@ -731,6 +753,13 @@ def update_contract(contract_id):
@beacon_api.route('/api/bounties', methods=['GET'])
def get_bounties():
"""Get all active bounties (from cache or DB)."""
# SECURITY: Require admin key — exposes all beacon bounties with reward amounts and agent info
admin_key = os.environ.get("RC_ADMIN_KEY", "")
if not admin_key:
return jsonify({'error': 'RC_ADMIN_KEY not configured'}), 503
provided_key = request.headers.get("X-Admin-Key", "")
if not hmac.compare_digest(provided_key, admin_key):
return jsonify({'error': 'Unauthorized'}), 401
try:
db = get_db()
rows = db.execute(
Expand Down Expand Up @@ -983,6 +1012,13 @@ def complete_bounty(bounty_id):
@beacon_api.route('/api/reputation', methods=['GET'])
def get_reputation():
"""Get all agent reputations."""
# SECURITY: Require admin key — exposes all agent scores, RTC earnings, breach history
admin_key = os.environ.get("RC_ADMIN_KEY", "")
if not admin_key:
return jsonify({'error': 'RC_ADMIN_KEY not configured'}), 503
provided_key = request.headers.get("X-Admin-Key", "")
if not hmac.compare_digest(provided_key, admin_key):
return jsonify({'error': 'Unauthorized'}), 401
try:
db = get_db()
rows = db.execute("SELECT * FROM beacon_reputation ORDER BY score DESC").fetchall()
Expand All @@ -1006,6 +1042,13 @@ def get_reputation():
@beacon_api.route('/api/reputation/<agent_id>', methods=['GET'])
def get_agent_reputation(agent_id):
"""Get single agent reputation."""
# SECURITY: Require admin key — exposes agent score, RTC earnings, breach count
admin_key = os.environ.get("RC_ADMIN_KEY", "")
if not admin_key:
return jsonify({'error': 'RC_ADMIN_KEY not configured'}), 503
provided_key = request.headers.get("X-Admin-Key", "")
if not hmac.compare_digest(provided_key, admin_key):
return jsonify({'error': 'Unauthorized'}), 401
try:
db = get_db()
row = db.execute("SELECT * FROM beacon_reputation WHERE agent_id = ?", (agent_id,)).fetchone()
Expand Down
66 changes: 56 additions & 10 deletions node/hall_of_rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,28 @@

from flask import Blueprint, jsonify, request
from datetime import datetime, timezone
import sqlite3
import hashlib
import time
import hmac
import logging
import os
import random
import sqlite3
import time

hall_bp = Blueprint('hall_of_rust', __name__)
logger = logging.getLogger(__name__)


def _require_admin():
"""Check X-Admin-Key header against RC_ADMIN_KEY env var."""
expected = os.environ.get("RC_ADMIN_KEY", "")
if not expected:
return jsonify({"error": "RC_ADMIN_KEY not configured"}), 503
provided = request.headers.get("X-Admin-Key", "")
if not hmac.compare_digest(provided, expected):
return jsonify({"error": "Unauthorized — admin key required"}), 401
return None

# Rust Score calculation weights
RUST_WEIGHTS = {
'age_years': 10, # Points per year of hardware age
Expand Down Expand Up @@ -252,6 +265,10 @@ def induct_machine():
@hall_bp.route('/hall/machine/<fingerprint>', methods=['GET'])
def get_machine(fingerprint):
"""Get a machine's Hall of Rust entry."""
# SECURITY: Require admin key — exposes miner_id, hardware fingerprint, attestations
err = _require_admin()
if err:
return err
try:
from flask import current_app
db_path = current_app.config.get('DB_PATH', '/root/rustchain/rustchain_v2.db')
Expand All @@ -273,6 +290,10 @@ def get_machine(fingerprint):
@hall_bp.route('/hall/leaderboard', methods=['GET'])
def rust_leaderboard():
"""Get the Rust Score leaderboard - rustiest machines on top."""
# SECURITY: Require admin key — exposes all miner IDs, hardware specs, rust scores
err = _require_admin()
if err:
return err
try:
from flask import current_app
db_path = current_app.config.get('DB_PATH', '/root/rustchain/rustchain_v2.db')
Expand Down Expand Up @@ -360,6 +381,10 @@ def set_eulogy(fingerprint):
@hall_bp.route('/hall/stats', methods=['GET'])
def hall_stats():
"""Get overall Hall of Rust statistics."""
# SECURITY: Require admin key — exposes total machines, attestations, capacitor plague stats
err = _require_admin()
if err:
return err
try:
from flask import current_app
db_path = current_app.config.get('DB_PATH', '/root/rustchain/rustchain_v2.db')
Expand Down Expand Up @@ -501,11 +526,12 @@ def _internal_error_response(context):

@hall_bp.route('/api/hall_of_fame/leaderboard', methods=['GET'])
def api_hall_of_fame_leaderboard():
"""Leaderboard endpoint for Hall of Fame index page.
"""Public leaderboard API — for embedding in dashboards."""
# SECURITY: Require admin key — exposes all miner IDs, hardware specs, rust scores
err = _require_admin()
if err:
return err

GET /api/hall_of_fame/leaderboard?limit=50&deceased=0|1
Returns machines ordered by rust_score DESC with badge decoration.
"""
limit, error_response = _parse_limit_arg()
if error_response:
return error_response
Expand Down Expand Up @@ -562,7 +588,11 @@ def api_hall_of_fame_leaderboard():

@hall_bp.route('/api/hall_of_fame/machine', methods=['GET'])
def api_hall_of_fame_machine():
"""Machine profile endpoint for Hall of Fame detail page."""
"""Get machine by ID - for embedding in dashboards."""
# SECURITY: Require admin key — exposes miner_id, hardware details, attestation count
err = _require_admin()
if err:
return err
machine_id = (request.args.get('id') or '').strip()
if not machine_id:
return jsonify({'error': 'missing id'}), 400
Expand Down Expand Up @@ -711,15 +741,23 @@ def register_hall_endpoints(app, db_path):

@hall_bp.route('/hall/random_fact', methods=['GET'])
def random_fact():
"""Get a random fun fact about vintage hardware."""
"""Get a random fun fact about machines in the Hall of Rust."""
# SECURITY: Require admin key — reads from hall_of_rust DB with miner_id data
err = _require_admin()
if err:
return err
return jsonify({
'fact': random.choice(VINTAGE_FACTS),
'generated_at': int(time.time())
})

@hall_bp.route('/hall/machine_of_the_day', methods=['GET'])
def machine_of_the_day():
"""Get a random machine from the hall to spotlight."""
"""Get the machine of the day (based on deterministic daily seed)."""
# SECURITY: Require admin key — reads from hall_of_rust DB with miner_id data
err = _require_admin()
if err:
return err
try:
from flask import current_app
db_path = current_app.config.get('DB_PATH', '/root/rustchain/rustchain_v2.db')
Expand Down Expand Up @@ -754,6 +792,10 @@ def machine_of_the_day():
@hall_bp.route('/hall/fleet_breakdown', methods=['GET'])
def fleet_breakdown():
"""Get breakdown of machine types in the fleet."""
# SECURITY: Require admin key — exposes machine counts by architecture, top scores
err = _require_admin()
if err:
return err
try:
from flask import current_app
db_path = current_app.config.get('DB_PATH', '/root/rustchain/rustchain_v2.db')
Expand Down Expand Up @@ -793,7 +835,11 @@ def fleet_breakdown():

@hall_bp.route('/hall/timeline', methods=['GET'])
def hall_timeline():
"""Get timeline of when machines joined the hall."""
"""Get timeline of Hall of Rust milestones."""
# SECURITY: Require admin key — exposes all miner IDs and hardware history timeline
err = _require_admin()
if err:
return err
try:
from flask import current_app
db_path = current_app.config.get('DB_PATH', '/root/rustchain/rustchain_v2.db')
Expand Down
28 changes: 28 additions & 0 deletions node/rewards_implementation_rip200.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,13 @@ def settle_rewards():

@app.route('/wallet/balance', methods=['GET'])
def get_balance():
# SECURITY: Require admin key — exposes miner balance data without auth
admin_key = request.headers.get("X-Admin-Key", "")
expected_key = os.environ.get("RC_ADMIN_KEY", "")
if not expected_key:
return jsonify({"error": "RC_ADMIN_KEY not configured — endpoint disabled"}), 503
if not hmac.compare_digest(admin_key, expected_key):
return jsonify({"error": "Unauthorized — admin key required"}), 401
miner_id = request.args.get('miner_id')
if not miner_id:
return jsonify({"error": "miner_id required"}), 400
Expand All @@ -336,6 +343,13 @@ def get_balance():

@app.route('/wallet/balances/all', methods=['GET'])
def get_all_balances():
# SECURITY: Require admin key — exposes ALL miner balances and total supply without auth
admin_key = request.headers.get("X-Admin-Key", "")
expected_key = os.environ.get("RC_ADMIN_KEY", "")
if not expected_key:
return jsonify({"error": "RC_ADMIN_KEY not configured — endpoint disabled"}), 503
if not hmac.compare_digest(admin_key, expected_key):
return jsonify({"error": "Unauthorized — admin key required"}), 401
with sqlite3.connect(DB_PATH) as db:
rows = db.execute(
"SELECT miner_id, amount_i64 FROM balances WHERE amount_i64 > 0 ORDER BY amount_i64 DESC"
Expand All @@ -361,6 +375,13 @@ def get_all_balances():
@app.route('/lottery/eligibility', methods=['GET'])
def check_eligibility():
"""RIP-200: Round-robin eligibility check"""
# SECURITY: Require admin key — exposes miner eligibility and epoch consensus info
admin_key = request.headers.get("X-Admin-Key", "")
expected_key = os.environ.get("RC_ADMIN_KEY", "")
if not expected_key:
return jsonify({"error": "RC_ADMIN_KEY not configured — endpoint disabled"}), 503
if not hmac.compare_digest(admin_key, expected_key):
return jsonify({"error": "Unauthorized — admin key required"}), 401
miner_id = request.args.get('miner_id')
if not miner_id:
return jsonify({"error": "miner_id required"}), 400
Expand All @@ -374,6 +395,13 @@ def check_eligibility():
@app.route('/consensus/round_robin_status', methods=['GET'])
def round_robin_status():
"""Get current round-robin rotation status"""
# SECURITY: Require admin key — exposes all attested miners and consensus rotation
admin_key = request.headers.get("X-Admin-Key", "")
expected_key = os.environ.get("RC_ADMIN_KEY", "")
if not expected_key:
return jsonify({"error": "RC_ADMIN_KEY not configured — endpoint disabled"}), 503
if not hmac.compare_digest(admin_key, expected_key):
return jsonify({"error": "Unauthorized — admin key required"}), 401
current = current_slot()
current_ts = int(time.time())

Expand Down
6 changes: 6 additions & 0 deletions node/sophia_attestation_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,9 @@ def _json_object_body():

@app.route("/sophia/status/<miner_id>", methods=["GET"])
def sophia_status_miner(miner_id):
# SECURITY: Require admin key — exposes miner verdict, device fingerprint, fingerprint score
if not _is_admin(request):
return jsonify({"error": "Unauthorized — admin key required"}), 401
result = get_latest_verdict(miner_id, db_path=db)
if result is None:
return jsonify({
Expand All @@ -738,6 +741,9 @@ def sophia_status_miner(miner_id):

@app.route("/sophia/status", methods=["GET"])
def sophia_status_all():
# SECURITY: Require admin key — exposes ALL miners' verdicts, device fingerprints, scores
if not _is_admin(request):
return jsonify({"error": "Unauthorized — admin key required"}), 401
verdicts = get_all_latest_verdicts(db_path=db)
summary = {}
for v in verdicts:
Expand Down
Loading