File size: 4,718 Bytes
6993919 d1b47a8 6993919 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | """
RMI Community Forensics Worker
Background processing for:
- Report verification pipeline
- Graph generation for forensic reports
- Cross-chain correlation batch jobs
- Community reputation updates
"""
import asyncio
import os
from datetime import UTC, datetime
import httpx
from app.core.logging import get_logger
logger = get_logger(__name__)
# Config
REDIS_HOST = os.getenv("REDIS_HOST", "rmi-redis")
REDIS_PORT = int(os.getenv("REDIS_PORT", 6379))
REDIS_PASSWORD = os.getenv("REDIS_PASSWORD", "")
REDIS_DB = int(os.getenv("REDIS_DB", 0))
BLOCKSCOUT_URL = os.getenv("BLOCKSCOUT_URL", "http://rmi-blockscout-backend:4000")
SUPABASE_URL = os.getenv("SUPABASE_URL", "")
SUPABASE_KEY = os.getenv("SUPABASE_KEY", "")
class CommunityForensicsWorker:
"""Background worker for community forensics tasks."""
def __init__(self):
self.blockscout = httpx.AsyncClient(base_url=BLOCKSCOUT_URL, timeout=30.0)
self.running = True
async def verify_report_graph(self, report_id: str, graph_data: dict) -> dict:
"""Verify and enrich a community report's graph data."""
# Validate graph structure
nodes = graph_data.get("nodes", [])
edges = graph_data.get("edges", [])
# Check for known scam addresses via Blockscout
enriched_nodes = []
for node in nodes:
addr = node.get("id", "")
if len(addr) >= 20: # Likely an address
try:
resp = await self.blockscout.get(f"/api/v2/addresses/{addr}")
if resp.status_code == 200:
data = resp.json()
node["verified_name"] = data.get("name", "")
node["tx_count"] = data.get("transactions_count", 0)
except Exception:
pass
enriched_nodes.append(node)
return {
"report_id": report_id,
"nodes_verified": len(enriched_nodes),
"edges_count": len(edges),
"enriched": True,
"timestamp": datetime.now(UTC).isoformat(),
}
async def batch_cross_chain_correlate(self, addresses: list[str]) -> list[dict]:
"""Batch cross-chain correlation for community investigations."""
results = []
for addr in addresses:
# Check multiple chains via Blockscout instances
chains = ["eth", "base", "bsc", "arbitrum"]
chain_hits = []
for chain in chains:
try:
# In production, each chain has its own Blockscout
# For now, query the main one
resp = await self.blockscout.get(f"/api/v2/addresses/{addr}")
if resp.status_code == 200:
chain_hits.append(chain)
except Exception:
pass
results.append(
{
"address": addr,
"chains_found": chain_hits,
"multi_chain": len(chain_hits) > 1,
}
)
return results
async def generate_leaderboard_snapshot(self) -> dict:
"""Generate daily leaderboard snapshot."""
# In production, query Supabase for real data
return {
"timestamp": datetime.now(UTC).isoformat(),
"period": "daily",
"top_sleuths": [],
}
async def run(self):
"""Main worker loop."""
logger.info("[OK] Community Forensics Worker started")
while self.running:
try:
# In production, read from Redis queue
# For now, health check loop
await asyncio.sleep(60)
# Health check Blockscout
try:
resp = await self.blockscout.get("/api/v2/main-page/indexing-status")
if resp.status_code == 200:
logger.info(f"[OK] Blockscout healthy: {resp.json().get('finished_indexing_blocks', False)}")
except Exception as e:
logger.warning(f"[WARN] Blockscout health check failed: {e}")
except Exception as e:
logger.warning(f"[ERROR] Worker loop: {e}")
await asyncio.sleep(5)
async def shutdown(self):
"""Graceful shutdown."""
self.running = False
await self.blockscout.aclose()
logger.info("[OK] Community Forensics Worker stopped")
async def main():
worker = CommunityForensicsWorker()
try:
await worker.run()
except KeyboardInterrupt:
await worker.shutdown()
if __name__ == "__main__":
asyncio.run(main())
|