-
-
Notifications
You must be signed in to change notification settings - Fork 451
RIP-309 Phase 1: Fingerprint check rotation (4-of-6) #2248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| name: RIP-309 Fingerprint Rotation CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - feature/rip309-fingerprint-rotation | ||
| pull_request: | ||
| branches: | ||
| - main | ||
|
|
||
| jobs: | ||
| rip309-tests: | ||
| name: RIP-309 Fingerprint Rotation + Settlement Integrity | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.11' | ||
|
|
||
| - name: Install node dependencies | ||
| run: pip install -r requirements-node.txt | ||
|
|
||
| - name: Run RIP-309 rotation tests | ||
| run: python node/tests/test_rip309_fingerprint_rotation.py -v | ||
|
|
||
| - name: Run settlement integrity tests | ||
| run: python node/tests/test_settlement_integrity.py -v |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,3 +1,6 @@ | ||||||
| import json | ||||||
| import random | ||||||
| import hashlib | ||||||
| #!/usr/bin/env python3 | ||||||
| """ | ||||||
| RIP-200: Round-Robin Consensus (1 CPU = 1 Vote) | ||||||
|
|
@@ -496,7 +499,8 @@ def calculate_epoch_rewards_time_aged( | |||||
| db_path: str, | ||||||
| epoch: int, | ||||||
| total_reward_urtc: int, | ||||||
| current_slot: int | ||||||
| current_slot: int, | ||||||
| prev_block_hash: bytes = b"", | ||||||
| ) -> Dict[str, int]: | ||||||
| """ | ||||||
| Calculate reward distribution for an epoch with time-aged multipliers | ||||||
|
|
@@ -519,6 +523,18 @@ def calculate_epoch_rewards_time_aged( | |||||
| Returns: | ||||||
| Dict of {miner_id: reward_urtc} | ||||||
| """ | ||||||
| # RIP-309: Rotating fingerprint checks (4-of-6 per epoch) | ||||||
| fp_checks = ['clock_drift', 'cache_timing', 'simd_identity', | ||||||
| 'thermal_drift', 'instruction_jitter', 'anti_emulation'] | ||||||
| if prev_block_hash: | ||||||
| nonce = hashlib.sha256(prev_block_hash + b"measurement_nonce").digest() | ||||||
| seed = int.from_bytes(nonce[:4], 'big') | ||||||
|
||||||
| seed = int.from_bytes(nonce[:4], 'big') | |
| seed = int.from_bytes(nonce, 'big') |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |||||||||||||||||||||||||||||||||||||
| import ipaddress | ||||||||||||||||||||||||||||||||||||||
| from urllib.parse import urlparse | ||||||||||||||||||||||||||||||||||||||
| from flask import Flask, request, jsonify, g, send_from_directory, send_file, abort, render_template_string, redirect | ||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||
| from beacon_anchor import init_beacon_table, store_envelope, compute_beacon_digest, get_recent_envelopes, VALID_KINDS | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| # Deployment compatibility: production may run this file as a single script. | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -2033,32 +2034,50 @@ def record_attestation_success(miner: str, device: dict, fingerprint_passed: boo | |||||||||||||||||||||||||||||||||||||
| _device["machine"] = "ppc64le" if "power8" in _miner_lower else "ppc" | ||||||||||||||||||||||||||||||||||||||
| verified_device = derive_verified_device(_device, fingerprint if isinstance(fingerprint, dict) else {}, fingerprint_passed) | ||||||||||||||||||||||||||||||||||||||
| with sqlite3.connect(DB_PATH) as conn: | ||||||||||||||||||||||||||||||||||||||
| # Ensure signing_pubkey column exists (idempotent migration) | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| conn.execute("ALTER TABLE miner_attest_recent ADD COLUMN signing_pubkey TEXT") | ||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||
| pass # Column already exists or table doesn't exist yet | ||||||||||||||||||||||||||||||||||||||
| # Ensure signing_pubkey and fingerprint_checks_json columns exist (idempotent migrations) | ||||||||||||||||||||||||||||||||||||||
| for col_stmt in [ | ||||||||||||||||||||||||||||||||||||||
| "ALTER TABLE miner_attest_recent ADD COLUMN signing_pubkey TEXT", | ||||||||||||||||||||||||||||||||||||||
| "ALTER TABLE miner_attest_recent ADD COLUMN fingerprint_checks_json TEXT", | ||||||||||||||||||||||||||||||||||||||
| "ALTER TABLE miner_attest_history ADD COLUMN fingerprint_checks_json TEXT", | ||||||||||||||||||||||||||||||||||||||
| ]: | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| conn.execute(col_stmt) | ||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||
| pass # Column already exists or table doesn't exist yet | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Extract per-check results from fingerprint dict for RIP-309 rotation. | ||||||||||||||||||||||||||||||||||||||
| fp_checks_map = {} | ||||||||||||||||||||||||||||||||||||||
| if isinstance(fingerprint, dict) and "checks" in fingerprint: | ||||||||||||||||||||||||||||||||||||||
| for k, v in fingerprint["checks"].items(): | ||||||||||||||||||||||||||||||||||||||
| fp_checks_map[k] = bool(v.get("passed", False)) if isinstance(v, dict) else bool(v) | ||||||||||||||||||||||||||||||||||||||
| # Also handle top-level flattened results if present | ||||||||||||||||||||||||||||||||||||||
| for k in ["clock_drift", "cache_timing", "simd_identity", "thermal_drift", "instruction_jitter", "anti_emulation"]: | ||||||||||||||||||||||||||||||||||||||
| if k in fingerprint: | ||||||||||||||||||||||||||||||||||||||
| fp_checks_map[k] = bool(fingerprint[k]) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2054
to
+2056
|
||||||||||||||||||||||||||||||||||||||
| for k in ["clock_drift", "cache_timing", "simd_identity", "thermal_drift", "instruction_jitter", "anti_emulation"]: | |
| if k in fingerprint: | |
| fp_checks_map[k] = bool(fingerprint[k]) | |
| if isinstance(fingerprint, dict): | |
| for k in ["clock_drift", "cache_timing", "simd_identity", "thermal_drift", "instruction_jitter", "anti_emulation"]: | |
| if k in fingerprint: | |
| fp_checks_map[k] = bool(fingerprint[k]) |
Copilot
AI
Apr 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RIP-309 active-check selection uses only nonce[:4] to build the RNG seed and uses __import__('random') for sampling. Using the full SHA256 digest as the seed avoids 32-bit collisions, and importing random normally is clearer/cheaper than dynamic import.
Copilot
AI
Apr 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prev_block_hash is computed as SHA256 over the ASCII hex string (message_hex.encode()), and the query fetches the current slot’s header (WHERE slot = ?). This is a non-standard “block hash” definition and may not match other components’ expectations for prev_block_hash. Consider hashing the underlying message bytes (hex-decode first) and (if intended) selecting the previous slot/header deterministically (e.g., slot-1 or the last header before epoch end).
| # Compute block hash from the current header message_hex as prev_block_hash | |
| prev_msg = db.execute( | |
| "SELECT message_hex FROM headers WHERE slot = ? ORDER BY slot DESC LIMIT 1", | |
| (slot,) | |
| ).fetchone() | |
| prev_block_hash = hashlib.sha256((prev_msg[0] if prev_msg else str(slot)).encode()).digest() if prev_msg else b"" | |
| # Compute prev_block_hash from the canonical last header in the epoch being settled. | |
| prev_msg = db.execute( | |
| "SELECT message_hex FROM headers WHERE slot >= ? AND slot < ? ORDER BY slot DESC LIMIT 1", | |
| (epoch_start, epoch_end) | |
| ).fetchone() | |
| if prev_msg and prev_msg[0]: | |
| try: | |
| prev_block_hash = hashlib.sha256(bytes.fromhex(prev_msg[0])).digest() | |
| except ValueError as hex_err: | |
| raise ValueError(f"Invalid message_hex for slot header used in epoch {current_epoch} settlement: {hex_err}") | |
| else: | |
| prev_block_hash = b"" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The shebang is no longer the first line of the file (imports were added above it). If this file is ever executed directly (./rip_200_round_robin_1cpu1vote.py), the OS may not invoke Python correctly. Move the shebang back to line 1 (or remove it if the file isn’t intended to be executed as a script).