semantic-scalpel-bsv / bsv_client.py
GotThatData's picture
Upload bsv_client.py with huggingface_hub
db7610c verified
"""
BSV Blockchain Client for Semantic Scalpel
Provides blockchain anchoring for inference provenance:
- Simulated mode: Generate realistic TXIDs for demo
- Live mode: Real BSV transactions via SimpleBSV API
Created by Bryan Daugherty
SmartLedger Blockchain Solutions Inc
"""
import hashlib
import json
import secrets
import time
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, Optional
from enum import Enum
try:
import httpx
HAS_HTTPX = True
except ImportError:
HAS_HTTPX = False
from config import get_config, BSVMode
class TransactionStatus(str, Enum):
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
FAILED = "failed"
SIMULATED = "simulated"
@dataclass
class BSVTransaction:
"""Result of a BSV blockchain operation."""
success: bool
txid: Optional[str] = None
status: TransactionStatus = TransactionStatus.PENDING
query_hash: str = ""
anchor_mode: str = "async"
fee_satoshis: int = 0
error: Optional[str] = None
timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat() + "Z")
is_simulated: bool = True
@property
def whatsonchain_url(self) -> Optional[str]:
"""Get WhatsOnChain explorer URL."""
if self.txid:
return f"https://whatsonchain.com/tx/{self.txid}"
return None
def to_dict(self) -> Dict[str, Any]:
return {
"success": self.success,
"txid": self.txid,
"status": self.status.value,
"query_hash": self.query_hash,
"anchor_mode": self.anchor_mode,
"fee_satoshis": self.fee_satoshis,
"error": self.error,
"timestamp": self.timestamp,
"is_simulated": self.is_simulated,
"whatsonchain_url": self.whatsonchain_url,
}
class BSVClient:
"""
BSV Blockchain client with simulated and live modes.
Usage:
client = BSVClient()
# Anchor a prediction
result = client.anchor_prediction(
text="The White House announced...",
prediction="Presidential administration",
confidence=0.95,
mode="async"
)
print(f"TXID: {result.txid}")
print(f"Verify: {result.whatsonchain_url}")
"""
def __init__(self):
self.config = get_config()
self._session = None
if self.config.bsv_mode == BSVMode.LIVE and HAS_HTTPX:
self._init_session()
def _init_session(self):
"""Initialize HTTP session for live BSV API."""
if not self.config.bsv_api_key:
return
self._session = httpx.Client(
timeout=30.0,
headers={
"Authorization": f"Bearer {self.config.bsv_api_key}",
"Content-Type": "application/json",
"User-Agent": "SemanticScalpel-BSV/1.0.0",
}
)
def _compute_query_hash(
self,
text: str,
prediction: str,
confidence: float,
) -> str:
"""Compute deterministic hash for a query."""
# Include hour to allow some caching but prevent replay attacks
content = f"{text}|{prediction}|{confidence:.4f}|{datetime.utcnow().strftime('%Y-%m-%d-%H')}"
return hashlib.sha256(content.encode()).hexdigest()
def _generate_simulated_txid(self) -> str:
"""Generate a realistic-looking BSV transaction ID."""
return secrets.token_hex(32)
# -------------------------------------------------------------------------
# Simulated Mode
# -------------------------------------------------------------------------
def _anchor_simulated(
self,
query_hash: str,
mode: str,
payload: Dict,
) -> BSVTransaction:
"""Create a simulated BSV transaction for demo purposes."""
# Simulate processing time based on mode
if mode == "sync":
time.sleep(0.1) # Simulate brief delay
return BSVTransaction(
success=True,
txid=self._generate_simulated_txid(),
status=TransactionStatus.SIMULATED,
query_hash=query_hash,
anchor_mode=mode,
fee_satoshis=200, # Typical BSV transaction fee
is_simulated=True,
)
# -------------------------------------------------------------------------
# Live Mode
# -------------------------------------------------------------------------
def _anchor_live(
self,
query_hash: str,
mode: str,
payload: Dict,
) -> BSVTransaction:
"""Anchor to real BSV blockchain via SimpleBSV API."""
if not self._session:
return BSVTransaction(
success=False,
error="BSV session not initialized (missing API key?)",
query_hash=query_hash,
anchor_mode=mode,
is_simulated=False,
)
# Build anchor payload
anchor_data = {
"protocol": "SmartLedger-Inference-v1",
"provider": "SmartLedger Blockchain Solutions Inc",
"model": "semantic-scalpel-v1",
"query_hash": query_hash,
"timestamp": datetime.utcnow().isoformat() + "Z",
"nist_control": "AU-10",
**payload,
}
try:
# Determine endpoint and params
url = f"{self.config.bsv_api_url}/publish/json"
if mode == "sync" or self.config.bsv_sync_mode:
url += "?wait=true"
response = self._session.post(
url,
json={"json": anchor_data, "signWithAIP": True},
)
if response.status_code == 401:
return BSVTransaction(
success=False,
error="Unauthorized: Invalid BSV API key",
query_hash=query_hash,
anchor_mode=mode,
is_simulated=False,
)
if response.status_code >= 400:
data = response.json() if response.content else {}
return BSVTransaction(
success=False,
error=data.get("error", f"HTTP {response.status_code}"),
query_hash=query_hash,
anchor_mode=mode,
is_simulated=False,
)
data = response.json()
return BSVTransaction(
success=data.get("success", False),
txid=data.get("txid"),
status=TransactionStatus(data.get("status", "pending")),
query_hash=query_hash,
anchor_mode=mode,
fee_satoshis=data.get("fee", 0),
is_simulated=False,
)
except httpx.TimeoutException:
return BSVTransaction(
success=False,
error="BSV API timeout",
query_hash=query_hash,
anchor_mode=mode,
is_simulated=False,
)
except Exception as e:
return BSVTransaction(
success=False,
error=str(e),
query_hash=query_hash,
anchor_mode=mode,
is_simulated=False,
)
# -------------------------------------------------------------------------
# Public API
# -------------------------------------------------------------------------
def anchor_prediction(
self,
text: str,
prediction: str,
confidence: float,
mode: str = "async",
metadata: Optional[Dict] = None,
) -> BSVTransaction:
"""
Anchor a prediction to BSV blockchain.
Args:
text: Input text that was analyzed
prediction: Model's prediction
confidence: Confidence score (0-1)
mode: Anchor mode (sync, async, batch)
metadata: Optional additional metadata
Returns:
BSVTransaction with TXID on success
"""
# Compute query hash
query_hash = self._compute_query_hash(text, prediction, confidence)
# Build payload (hash only - no raw text on chain)
payload = {
"prediction_hash": hashlib.sha256(prediction.encode()).hexdigest()[:16],
"confidence_bucket": "high" if confidence >= 0.85 else "medium" if confidence >= 0.70 else "low",
}
if metadata:
payload["metadata"] = metadata
# Route to appropriate handler
if self.config.bsv_mode == BSVMode.LIVE:
return self._anchor_live(query_hash, mode, payload)
else:
return self._anchor_simulated(query_hash, mode, payload)
def health_check(self) -> Dict[str, Any]:
"""Check BSV API health (live mode only)."""
if self.config.bsv_mode != BSVMode.LIVE or not self._session:
return {
"status": "simulated",
"mode": self.config.bsv_mode.value,
"message": "BSV in simulated mode - no live connection",
}
try:
response = self._session.get(f"{self.config.bsv_api_url}/health")
if response.status_code == 200:
data = response.json()
return {
"status": "healthy",
"mode": "live",
**data,
}
return {
"status": "degraded",
"mode": "live",
"http_status": response.status_code,
}
except Exception as e:
return {
"status": "error",
"mode": "live",
"error": str(e),
}
def get_mode_info(self) -> Dict[str, Any]:
"""Get current BSV mode information."""
return {
"mode": self.config.bsv_mode.value,
"is_live": self.config.bsv_mode == BSVMode.LIVE,
"api_url": self.config.bsv_api_url if self.config.bsv_mode == BSVMode.LIVE else None,
"has_api_key": bool(self.config.bsv_api_key),
"sync_mode": self.config.bsv_sync_mode,
}
# Global client instance
_client: Optional[BSVClient] = None
def get_bsv_client() -> BSVClient:
"""Get or create the global BSV client instance."""
global _client
if _client is None:
_client = BSVClient()
return _client