diff --git a/apps/AgentAuditor/.env.example b/apps/AgentAuditor/.env.example new file mode 100644 index 0000000..023e10d --- /dev/null +++ b/apps/AgentAuditor/.env.example @@ -0,0 +1,32 @@ +ARBITRUM_SEPOLIA_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc +PRIVATE_KEY= +SESSION_V2_ADDRESS=0x2e9cC638CF07efdeC82b4beF932Ca4a8Dcd55015 +SESSION_QUEUE_V2_ADDRESS=0x9a9Db957e106894D598bf3AD912F3B604C085235 +DATABASE_URL=sqlite:///./agent_auditor.db +IPFS_PROVIDER=pinata +PINATA_API_KEY= +PINATA_SECRET_KEY= +CORTENSOR_RPC_URL=https://rpc.cortensor.network + +CORTENSOR_SESSION_ID= + +CORTENSOR_API_URL=http://172.29.51.244:5010 + +# Enable API +API_ENABLE=1 + +# Generate a unique API key +API_KEY=default-dev-token + +# Set your API port +API_PORT=8000 + +# Router External IP and Port for Miner Communication +# Used for external access to the router +ROUTER_EXTERNAL_IP="192.168.250.221" +ROUTER_EXTERNAL_PORT="9001" + +# Router REST Bind IP and Port for Client Communication +# Reverse proxy to this IP and port +ROUTER_REST_BIND_IP="127.0.0.1" +ROUTER_REST_BIND_PORT="5010" \ No newline at end of file diff --git a/apps/AgentAuditor/.gitignore b/apps/AgentAuditor/.gitignore new file mode 100644 index 0000000..8e90e6f --- /dev/null +++ b/apps/AgentAuditor/.gitignore @@ -0,0 +1,73 @@ +# Environment +.env +.env.* +!.env.example + +# Python +__pycache__/ +*.py[cod] +*$py.class +.Python +venv/ +env/ +.venv/ +pip-wheel-metadata/ +pip-log.txt + +# Packaging / build +build/ +dist/ +*.egg-info/ +.eggs/ + +# Tests / coverage +.pytest_cache/ +.coverage +htmlcov/ +coverage/ + +# Databases +*.sqlite3 +*.db +*.sqlite + +# Logs +*.log +logs/ + +# IDEs / editors +.vscode/ +.idea/ +*.iml + +# OS files +.DS_Store +Thumbs.db + +# Node / frontend +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +package-lock.json +yarn.lock +/dist +/.vite + +# Docker +docker-compose.override.yml + +# Jupyter +.ipynb_checkpoints + +# Secrets / keys / certificates +*.pem +*.key +private_key +secrets.json + +# Misc +*.bak +*.swp +```# filepath: c:\Users\shres\Desktop\AgentAuditor\.gitignore diff --git a/apps/AgentAuditor/README.md b/apps/AgentAuditor/README.md new file mode 100644 index 0000000..c3e5d5e --- /dev/null +++ b/apps/AgentAuditor/README.md @@ -0,0 +1,505 @@ +# AgentAuditor: Decentralized AI Compliance Oracle + +> **The Trust Layer for Decentralized AI Agents** +> A Client-Side Verification Oracle that validates AI task execution through independent PoI/PoUW consensus verification and generates cryptographically signed audit proofs. + +[](https://cortensor.ai) +[](https://sepolia.arbiscan.io) +[](https://ipfs.io) + +--- + +Video Demo - https://youtu.be/GhqxvqlBqbM + +## ๐ฏ The Problem: The AI Trust Gap + +As decentralized AI scales toward enterprise adoption, three critical barriers emerge: + +### 1. **Opaque Execution** +When miners execute AI tasks (like "generate a function to calculate X" or "analyze data Y"), how do external parties verify the work was done correctly and not hallucinated or manipulated? + +### 2. **Ephemeral Consensus** +Cortensor validators perform real-time consensus through PoI (embedding similarity checks across 3+ validators) and PoUW (quality scoring by 1 validator across 3 criteria). But once a session closes, this validation signal becomes difficult to audit or present to third parties. + +### 3. **Enterprise Compliance Gap** +Organizations need **verifiable receipts** before they can: +- Integrate AI agents into regulated workflows +- Trigger smart contract payments based on AI outputs +- Maintain audit trails for compliance (GDPR, SOX, etc.) +- Build liability frameworks around AI decisions + +**Current Reality**: Decentralized AI networks excel at execution and consensus but lack portable proof mechanisms for downstream consumers. + +--- + +## ๐ก The Solution: AgentAuditor + +AgentAuditor is a **Decentralized AI Oracle** and **Client-Side Verification Layer** that bridges the gap between Cortensor's network consensus and enterprise-grade trust. + +### We Ask a Different Question + +Traditional oracles ask: *"What is the truth?"* (e.g., "What's the BTC price?") +**AgentAuditor asks**: *"Did miners execute this AI task correctly according to network consensus?"* + +### How It Works + +``` +User Request โ Miners Execute Task โ Network Validates (PoI/PoUW) โ AgentAuditor Verifies โ Proof-of-Audit +``` + +**The Verification Process:** + +1. **Task Execution**: User submits a task (e.g., "Generate Python function to calculate Fibonacci sequence" or "Analyze this dataset for outliers") + +2. **Miner Inference**: Multiple miners execute the AI inference task independently + +3. **Network Consensus**: + - **PoI (Proof of Inference)**: 3+ validators check embedding vector similarity between miner outputs using cosine similarity/Euclidean distance (threshold typically 0.85+) + - **PoUW (Proof of Useful Work)**: 1 validator scores output across 3 criteria: semantic consistency, logical coherence, and practical applicability + +4. **Client-Side Verification**: AgentAuditor independently: + - Retrieves raw outputs and validation scores from all miners/validators + - Re-computes embedding similarities using local sentence-transformer models + - Validates that consensus thresholds were actually met + - Checks for anomalies (single-miner dominance, suspiciously uniform responses) + +5. **Evidence Generation**: Bundles everything into a signed, immutable audit artifact + +**Output**: A portable, tamper-proof **Proof-of-Audit** certificateโthe "receipt" that enterprises need. + +--- + +## ๐๏ธ Architecture + +AgentAuditor operates as a **Layer-2 Application** on Cortensor, adding independent verification and notarization: + +```mermaid +graph TD + User[Enterprise Client / dApp] -->|Submit Audit Task| UI[AgentAuditor Dashboard] + UI -->|REST API| Backend[FastAPI Orchestrator] + + subgraph "AgentAuditor Verification Layer" + Backend -->|1. Task Submission| Router[Cortensor Router] + Backend -->|2. Retrieve Raw Data| Router + Backend -->|3. Independent Verification| Verify[PoI/PoUW Verification Engines] + Backend -->|4. Evidence Generation| Gen[Evidence Generator] + Backend -->|5. Audit Storage| DB[(PostgreSQL)] + end + + subgraph "Cortensor Consensus Layer" + Router -->|Interact via SessionV2| Session[SessionV2 Contract] + Session -->|Queue Tasks| Queue[SessionQueueV2 Contract] + Queue -->|Broadcast| Blockchain[Arbitrum Sepolia] + Blockchain -->|Assign to Miners| Miners[Multiple Miner Nodes] + Miners -->|Submit Outputs| Validators[PoI/PoUW Validators] + Validators -->|Consensus Scores| Router + end + + subgraph "Trust Infrastructure" + Gen -->|Upload Bundle| IPFS[IPFS via Pinata] + Gen -->|Cryptographic Signing| Wallet[Web3 Wallet] + end + + Router -->|Raw Outputs + Scores| Backend + Verify -->|Validated โ| Gen + + style Session fill:#4a90e2 + style Queue fill:#4a90e2 + style Router fill:#50c878 + style Validators fill:#e74c3c +``` + +**Key Insight**: AgentAuditor doesn't deploy custom smart contracts or replace consensus. Instead, it acts as an independent auditor that retrieves, verifies, and notarizes the network's existing validation results using Cortensor's native **SessionV2** and **SessionQueueV2** contracts. + +--- + +## ๐ Understanding PoI and PoUW in AgentAuditor + +### Proof of Inference (PoI) - Consistency Verification + +PoI validates that miners used the correct models and produced consistent outputs by measuring embedding vector distance. When miners execute the same task, their outputs should be semantically similar even if syntactically different. + +**How AgentAuditor Verifies PoI:** +1. Retrieves outputs from all miners (e.g., 3+ miners) +2. Converts each output to embeddings using sentence-transformers +3. Computes similarity matrix using cosine similarity +4. Validates that similarity scores meet threshold (typically >0.85) +5. Flags outputs that deviate significantly from consensus + +**Example**: If 3 miners generate functions for Fibonacci calculation, the embedding vectors should show high cosine similarity even if variable names or syntax differ. + +### Proof of Useful Work (PoUW) - Quality Verification + +PoUW validates the correctness, usefulness, and semantic quality of outputs using additional LLM models. A validator scores the output across multiple dimensions. + +**How AgentAuditor Verifies PoUW:** +1. Retrieves validator scores across 3 criteria: + - **Semantic Consistency**: Does the output make logical sense? + - **Logical Coherence**: Is the reasoning sound? + - **Practical Applicability**: Is the result actually useful? +2. Validates score authenticity and checks for manipulation +3. Records all scoring data in the audit bundle + +**Why Independent Verification Matters**: AgentAuditor doesn't blindly trust the Router's reported consensus. It independently re-computes PoI similarities and validates PoUW scores, protecting against: +- Compromised Router nodes +- API manipulation attacks +- False consensus reporting +- Lazy validation + +--- + +## ๐ Core Features + +### 1. **Protocol-Native Integration** + +AgentAuditor showcases **production-grade Cortensor integration** by using the protocol's native smart contracts: + +**SessionV2 Contract Integration:** +- Manages AI task lifecycle through state machine (Request โ Precommit โ Commit โ End) +- Handles $COR token deposits and payment flows automatically +- Provides cryptographic proofs (TaskRoot per task, SessionRoot for history) +- Configurable accuracy and validation levels for different use cases + +**SessionQueueV2 Contract Integration:** +- Decentralized job scheduler for optimal miner assignment +- Manages ephemeral and dedicated miner selection from Node Pool +- Validates task integrity with per-task Merkle roots +- Ensures reliable task distribution across the network + +**Why This Approach Wins:** +- โ No custom smart contract deployment (reduced attack surface) +- โ Automatic compatibility with protocol upgrades +- โ Gas-efficient operations using battle-tested code +- โ Demonstrates deep understanding of Cortensor architecture + +### 2. **Independent Consensus Verification** + +AgentAuditor's verification engines provide defense-in-depth: + +**PoI Engine:** +- Downloads raw miner outputs from Router/IPFS +- Generates embeddings using local sentence-transformer models +- Computes full similarity matrix independently +- Compares client-side results against reported network consensus +- Detects anomalies like single-miner dominance or fake consensus + +**PoUW Engine:** +- Retrieves validator scoring data +- Validates score authenticity and reasonable distributions +- Checks for score manipulation patterns +- Records all quality metrics in audit bundle + +**Value**: Provides cryptographic proof that consensus was achieved honestly, not just reported by the Router. + +### 3. **Immutable Evidence Bundles** + +Every audit generates a comprehensive JSON bundle stored on IPFS: + +```json +{ + "audit_id": "uuid-here", + "timestamp": "2025-12-28T10:30:00Z", + "agent_id": "code-generator-bot", + "task": { + "prompt": "Generate a Python function to calculate Fibonacci sequence", + "input_data": {"max_n": 100}, + "task_type": "code_generation" + }, + "miner_outputs": [ + { + "miner_id": "0xMiner1...", + "output": "def fibonacci(n):\n if n <= 1: return n\n return fibonacci(n-1) + fibonacci(n-2)", + "embedding": [0.234, -0.567, ...] + }, + {...} + ], + "consensus_verification": { + "poi_validation": { + "similarity_matrix": [[1.0, 0.92, 0.89], [0.92, 1.0, 0.91], [0.89, 0.91, 1.0]], + "threshold": 0.85, + "passed": true, + "validator_count": 3 + }, + "pouw_validation": { + "semantic_consistency": 0.94, + "logical_coherence": 0.91, + "practical_applicability": 0.88, + "overall_score": 0.91, + "validator_id": "0xValidator1..." + } + }, + "cortensor_session": { + "session_id": 147, + "session_v2_address": "0x...", + "block_number": 12345 + }, + "signature": { + "auditor_address": "0x...", + "signature": "0x...", + "message_hash": "0x..." + }, + "ipfs_cid": "Qm..." +} +``` + +**Value**: Creates a legal-grade, tamper-proof audit trail that survives beyond the network's operational lifetime. + +### 4. **Compliance Dashboard** + +React-based explorer enabling: + +- **Agent Registry**: Browse registered AI agents with reputation scores +- **Audit History**: View complete task execution history with filtering +- **Evidence Inspection**: Drill into raw IPFS bundles with JSON viewer +- **Consensus Visualization**: See similarity matrices and validator scores +- **Signature Verification**: On-chain verification of cryptographic signatures +- **Compliance Reports**: Export audit trails for regulatory review + +**Value**: Makes decentralized AI observable, transparent, and auditable for enterprise stakeholders. + +### 5. **Smart Contract Integration (Roadmap)** + +Future capability to trigger on-chain logic based on audit results: + +```solidity +function releasePayment(bytes32 auditHash, bytes signature) external { + require(AgentAuditor.verifyAudit(auditHash, signature), "Audit failed"); + require(AgentAuditor.poiPassed(auditHash), "PoI consensus not met"); + require(AgentAuditor.pouwScore(auditHash) >= 0.85, "Quality below threshold"); + // Release escrowed payment +} +``` + +--- + +## ๐ ๏ธ Installation & Setup + +### Prerequisites + +- **Python 3.9+** and **Node.js 18+** +- **Cortensor Router Binary** (running locally) +- **Cortensor Miner Node** (for local testing) +- **Arbitrum Sepolia Wallet** with test $ETH and $COR +- **Pinata Account** (optional for IPFS; can use local node) + +### 1. Clone Repository + +```bash +git clone https://github.com/yourusername/AgentAuditor.git +cd AgentAuditor +``` + +### 2. Backend Setup + +```bash +cd backend +python -m venv venv +source venv/bin/activate # Windows: .\venv\Scripts\activate +pip install -r requirements.txt +``` + +**Environment Configuration** (`.env` file): + +```bash +# Cortensor Infrastructure +CORTENSOR_API_URL=http://127.0.0.1:5010 +CORTENSOR_SESSION_ID=147 # From Cortensor Dashboard + +# Blockchain (Cortensor Protocol Contracts) +ARBITRUM_SEPOLIA_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc +PRIVATE_KEY=your_wallet_private_key_here + +# Native Cortensor Smart Contracts (Testnet Addresses) +# Using protocol's native contracts instead of custom deployment +SESSION_V2_ADDRESS=0x... # Cortensor SessionV2 - manages task lifecycle & payments +SESSION_QUEUE_V2_ADDRESS=0x... # Cortensor SessionQueueV2 - handles miner orchestration +# Check latest addresses: https://docs.cortensor.network/ + +# Storage (Required for Evidence features) +PINATA_API_KEY=your_pinata_api_key +PINATA_SECRET_KEY=your_pinata_secret_key + +# Database +DATABASE_URL=postgresql://user:pass@localhost/agentauditor +``` + +### 3. Frontend Setup + +```bash +cd frontend +npm install +``` + +### 4. Run the Complete Stack + +**Terminal 1 - Cortensor Router:** +```bash +./cortensor-router +# Wait for: "* Found active session: 147" +``` + +**Terminal 2 - Cortensor Miner:** +```bash +./cortensor-node --mode ephemeral +``` + +**Terminal 3 - Backend:** +```bash +cd AgentAuditor +python -m backend.main +``` + +**Terminal 4 - Frontend:** +```bash +cd frontend +npm run dev +``` + +Access dashboard at: `http://localhost:3000` + +--- + +## ๐ Usage Guide + +### Step 1: Dashboard Session Setup + +1. Visit [Cortensor Dashboard](https://dashboard.cortensor.ai) +2. Create a new Session and deposit test $COR +3. Copy the Session ID to your `.env` file + +### Step 2: Submit an Audit + +1. Navigate to `http://localhost:3000/submit` +2. Enter: + - **Agent ID**: `code-generator-bot` (or your registered agent) + - **Task Type**: Select from dropdown (code generation, data analysis, text generation) + - **Task Prompt**: `"Generate a Python function to calculate factorial"` + - **Input Data**: Optional JSON parameters +3. Click **Submit Audit** + +### Step 3: Monitor Execution + +The system executes the following workflow: + +1. **Task Broadcast**: Router receives task via SessionV2 contract +2. **Miner Assignment**: SessionQueueV2 assigns task to 3+ ephemeral miners +3. **Inference Execution**: Miners independently execute AI inference +4. **Network Validation**: + - PoI validators compute embedding similarities + - PoUW validator scores quality across 3 criteria +5. **Client-Side Verification**: + - AgentAuditor retrieves all raw outputs + - Re-computes embeddings locally + - Validates consensus independently +6. **Evidence Generation**: + - Bundles all data into JSON + - Signs with auditor's private key + - Uploads to IPFS + +**Real-time status updates appear in the dashboard.** + +### Step 4: Verify Evidence + +1. Navigate to **Audit Explorer** +2. Click on your completed audit +3. Review: + - PoI similarity matrix (should show >0.85 across miners) + - PoUW scores (semantic/logical/practical) + - Individual miner outputs + - Consensus validation status +4. Click **View on IPFS** to inspect raw signed bundle +5. Use **Verify Signature** to cryptographically validate authenticity + +--- + +## ๐ Hackathon Alignment + +**How AgentAuditor Addresses Hackathon Goals:** + +| Requirement | Implementation | +|-------------|----------------| +| **Novel Router Usage** | Uses Router as data source for independent consensus verification | +| **Oracle Functionality** | Provides deterministic answers to "Did miners execute this task correctly?" | +| **Enterprise Value** | Creates legally-admissible audit trails for compliance | +| **Protocol Understanding** | Leverages SessionV2/SessionQueueV2 contracts natively (best practice) | +| **Consensus Innovation** | Client-side verification of PoI/PoUW adds trust layer above network | + +### Differentiation + +**Unlike typical submissions:** + +- โ **Not just a chat interface**: We build trust infrastructure, not UX sugar +- โ **Not a trivia oracle**: We verify AI task execution, not static facts +- โ **Not consensus-replacement**: We're an independent auditor that validates existing consensus +- โ **Not reinventing contracts**: We use SessionV2/SessionQueueV2 properly (best practice) +- โ **Not blindly trusting**: We independently re-compute PoI and validate PoUW + +**Our Unique Value:** + +โ **First** to create independent client-side verification of Cortensor consensus +โ **First** to generate cryptographically signed audit artifacts for AI tasks +โ **First** to solve the enterprise compliance gap for decentralized AI +โ **Best Practice**: Building *with* protocol contracts and *validating* network consensus +โ **True Oracle**: Answers verifiable questions about AI agent behavior, not just data retrieval + +--- + +## ๐ฎ Roadmap + +### Phase 1: Foundation (Current) +- โ Independent PoI embedding verification +- โ PoUW score validation +- โ Evidence bundle generation +- โ IPFS storage with Pinata +- โ Cryptographic signing + +### Phase 2: Smart Contract Integration (Q1 2025) +- ๐จ On-chain audit verification contract +- ๐จ Payment escrow triggered by PoI/PoUW thresholds +- ๐จ Multi-signature auditor network + +### Phase 3: Privacy Layer (Q2 2025) +- ๐ฎ ZK-SNARK proofs for private audits +- ๐ฎ Homomorphic encryption for sensitive task data +- ๐ฎ Selective disclosure of evidence bundles + +### Phase 4: Decentralized Auditor Network (Q3 2025) +- ๐ฎ Multi-router consensus aggregation +- ๐ฎ Reputation-weighted auditor selection +- ๐ฎ Slashing for fraudulent audit reports + +--- + +## ๐ค Contributing + +We welcome contributions! Key areas: + +- **Verification Algorithms**: Improve PoI embedding similarity checks +- **PoUW Validation**: Enhanced quality scoring mechanisms +- **Smart Contracts**: Help build on-chain verification +- **Privacy Features**: Implement ZK-proof systems + +--- + +## ๐ License + +MIT License - See `LICENSE` file for details + +--- + +## ๐ Acknowledgments + +- **Cortensor Team**: For building the decentralized AI infrastructure and comprehensive documentation +- **Arbitrum**: For providing a scalable L2 for smart contracts +- **IPFS/Pinata**: For decentralized storage infrastructure + +--- + +## ๐ Contact + +- **GitHub**: [github.com/yourusername/AgentAuditor](https://github.com/Shreshtthh/AgentAuditor) +- **Demo Video**: [Link to demo] + +--- + +**Built with โค๏ธ for the Cortensor Hackathon** diff --git a/apps/AgentAuditor/SessionQueueV2ABI.json b/apps/AgentAuditor/SessionQueueV2ABI.json new file mode 100644 index 0000000..4cc9ccb --- /dev/null +++ b/apps/AgentAuditor/SessionQueueV2ABI.json @@ -0,0 +1,1950 @@ + [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "miner", + "type": "address" + } + ], + "name": "TaskAcked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "miners", + "type": "address[]" + } + ], + "name": "TaskAllCommitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "miners", + "type": "address[]" + } + ], + "name": "TaskAllPrecommitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "miners", + "type": "address[]" + } + ], + "name": "TaskAssigned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "TaskCommitEnded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "miner", + "type": "address" + } + ], + "name": "TaskCommitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "miners", + "type": "address[]" + } + ], + "name": "TaskEnded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "TaskPrecommitEnded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "miner", + "type": "address" + } + ], + "name": "TaskPrecommitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "globalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "taskData", + "type": "string" + } + ], + "name": "TaskQueued", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "ack", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "addrs", + "type": "address[]" + } + ], + "name": "assignTask", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "data", + "type": "string" + } + ], + "name": "commit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "forceEnd", + "type": "bool" + } + ], + "name": "end", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "forceEnd", + "type": "bool" + } + ], + "name": "end2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "endAck", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "emitAllCommitted", + "type": "bool" + } + ], + "name": "endCommit", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "emitAllPrecommitted", + "type": "bool" + } + ], + "name": "endPrecommit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "taskData", + "type": "string" + }, + { + "internalType": "uint256", + "name": "promptType", + "type": "uint256" + }, + { + "internalType": "string", + "name": "promptTemplate", + "type": "string" + }, + { + "internalType": "uint256[]", + "name": "llmParams", + "type": "uint256[]" + } + ], + "name": "enqueueTask", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getACLAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getAllTaskResultAckedTimestamps", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getAllTaskResultCommitTimestamps", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getAllTaskResultPrecommitTimestamps", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getAllTaskResults", + "outputs": [ + { + "internalType": "address[][]", + "name": "", + "type": "address[][]" + }, + { + "internalType": "string[][]", + "name": "", + "type": "string[][]" + }, + { + "internalType": "bytes32[][]", + "name": "", + "type": "bytes32[][]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "str", + "type": "string" + } + ], + "name": "getHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getIAMAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getLatestTask", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "getNodeId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getNodePoolAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNodeReputationAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNodeStatsAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSessionQueueValidationAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSessionReputationAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSessionStatsAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getTaskCountBySessionId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "sessionIds", + "type": "uint256[]" + } + ], + "name": "getTaskCountBySessionIds", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskData", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskDecoration", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "promptType", + "type": "uint256" + }, + { + "internalType": "string", + "name": "promptTemplate", + "type": "string" + }, + { + "internalType": "uint256", + "name": "maxTokens", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "temperature", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "topP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "topK", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "presencePenalty", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "frequencyPenalty", + "type": "uint256" + } + ], + "internalType": "struct SessionQueueV2.TaskDecoration", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskDecorationFrequencyPenalty", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskDecorationMaxTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskDecorationPresencePenalty", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskDecorationPromptTemplate", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskDecorationPromptType", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskDecorationTemperature", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskDecorationTopK", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskDecorationTopP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "miner", + "type": "address" + } + ], + "name": "getTaskResultCommitTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "miner", + "type": "address" + } + ], + "name": "getTaskResultData", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskResultHashes", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "miner", + "type": "address" + } + ], + "name": "getTaskResultPrecommitTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "getTaskResultState", + "outputs": [ + { + "internalType": "enum SessionQueueV2.TaskState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskResults", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "string[]", + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskStatus", + "outputs": [ + { + "internalType": "enum SessionQueueV2.TaskStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getTasksBySessionId", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "data", + "type": "string" + }, + { + "internalType": "address[]", + "name": "assignedMiners", + "type": "address[]" + }, + { + "internalType": "bool", + "name": "ended", + "type": "bool" + }, + { + "internalType": "enum SessionQueueV2.TaskStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "createdAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastPrecommitAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastCommitAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "finishedAt", + "type": "uint256" + } + ], + "internalType": "struct SessionQueueV2.Task[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getUseSessionStats", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "isAcked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "isOracleNode", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "isPrimaryModule", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "pop", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "popSpecificTask", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "data", + "type": "string" + } + ], + "name": "precommit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sessionTaskCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sessionTaskDecorations", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "promptType", + "type": "uint256" + }, + { + "internalType": "string", + "name": "promptTemplate", + "type": "string" + }, + { + "internalType": "uint256", + "name": "maxTokens", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "temperature", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "topP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "topK", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "presencePenalty", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "frequencyPenalty", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sessionTaskResults", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "precommitCounter", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "commitCounter", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sessionTasks", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "data", + "type": "string" + }, + { + "internalType": "bool", + "name": "ended", + "type": "bool" + }, + { + "internalType": "enum SessionQueueV2.TaskStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "createdAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastPrecommitAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastCommitAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "finishedAt", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_aclContract", + "type": "address" + } + ], + "name": "setACL", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_iamContract", + "type": "address" + } + ], + "name": "setIAM", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodePoolContract", + "type": "address" + } + ], + "name": "setNodePool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeReputationContract", + "type": "address" + } + ], + "name": "setNodeReputation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeStatsContract", + "type": "address" + } + ], + "name": "setNodeStats", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sessionQueueValidationContract", + "type": "address" + } + ], + "name": "setSessionQueueValidation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sessionReputationContract", + "type": "address" + } + ], + "name": "setSessionReputation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sessionStatsContract", + "type": "address" + } + ], + "name": "setSessionStats", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_useSessionStats", + "type": "bool" + } + ], + "name": "setUseSessionStats", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "subversion", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "useSessionStats", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "str", + "type": "string" + }, + { + "internalType": "bytes32", + "name": "expectedHash", + "type": "bytes32" + } + ], + "name": "verifyHash", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/apps/AgentAuditor/SessionV2ABI.json b/apps/AgentAuditor/SessionV2ABI.json new file mode 100644 index 0000000..ab94f02 --- /dev/null +++ b/apps/AgentAuditor/SessionV2ABI.json @@ -0,0 +1,2257 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "AllRoutersRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeAddress", + "type": "address" + } + ], + "name": "DedicatedNodeAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeAddress", + "type": "address" + } + ], + "name": "DedicatedNodeRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DepositToSession", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "nodeId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "NodeReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "routerAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "routerInfo", + "type": "string" + } + ], + "name": "RouterAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "routerAddress", + "type": "address" + } + ], + "name": "RouterRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "routerAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "routerInfo", + "type": "string" + } + ], + "name": "RouterUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sid", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "miners", + "type": "address[]" + } + ], + "name": "SessionCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "deactivator", + "type": "address" + } + ], + "name": "SessionDeactivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "updater", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "minNumOfNodes", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxNumOfNodes", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "redundant", + "type": "uint256" + } + ], + "name": "SessionUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32[]", + "name": "nodeIds", + "type": "bytes32[]" + } + ], + "name": "TaskAssigned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "taskData", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "clientReference", + "type": "string" + } + ], + "name": "TaskSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "WithdrawFromSession", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "nodeAddress", + "type": "address" + } + ], + "name": "addDedicatedNode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "routerAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "metadata", + "type": "string" + } + ], + "name": "addRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allNodeAddresses", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "internalType": "address", + "name": "variableAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "minNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "redundant", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "numOfValidatorNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mode", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "reserveEphemeralNodes", + "type": "bool" + } + ], + "name": "create", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "deactivateSession", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "doesSessionExist", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getACLAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "userAddr", + "type": "address" + } + ], + "name": "getActiveSessionsByAddress", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "sid", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "internalType": "enum SessionV2.SessionState", + "name": "state", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "createdAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address[]", + "name": "ephemeralNodes", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "dedicatedNodes", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "mode", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "redundant", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "numOfValidatorNodes", + "type": "uint256" + }, + { + "internalType": "string[]", + "name": "routerMetadatas", + "type": "string[]" + }, + { + "internalType": "address[]", + "name": "routerAddresses", + "type": "address[]" + } + ], + "internalType": "struct SessionV2.Session[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getDedicatedNodeCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getDedicatedNodes", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getEphemeralNodes", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getIAMAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getLatestRouterAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getLatestRouterIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getLatestRouterInfo", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "userAddr", + "type": "address" + } + ], + "name": "getLatestSessionId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getLatestTaskRequest", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requestDate", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "completed", + "type": "bool" + }, + { + "internalType": "string", + "name": "data", + "type": "string" + } + ], + "internalType": "struct SessionV2.TaskRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getMode", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "nodeAddress", + "type": "address" + } + ], + "name": "getNodeAssignedTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "getNodeId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getNodePoolAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "nodeAddress", + "type": "address" + } + ], + "name": "getNodeSessionId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRouterAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getRouterCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "routerAddress", + "type": "address" + } + ], + "name": "getRouterIndex", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "routerAddress", + "type": "address" + } + ], + "name": "getRouterInfo", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "userAddr", + "type": "address" + } + ], + "name": "getRouterMetadatasByAddress", + "outputs": [ + { + "internalType": "string[]", + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "sessionIds", + "type": "uint256[]" + } + ], + "name": "getRouterMetadatasBySessionIds", + "outputs": [ + { + "internalType": "string[]", + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "getSession", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "sid", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "internalType": "enum SessionV2.SessionState", + "name": "state", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "createdAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address[]", + "name": "ephemeralNodes", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "dedicatedNodes", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "mode", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "redundant", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "numOfValidatorNodes", + "type": "uint256" + }, + { + "internalType": "string[]", + "name": "routerMetadatas", + "type": "string[]" + }, + { + "internalType": "address[]", + "name": "routerAddresses", + "type": "address[]" + } + ], + "internalType": "struct SessionV2.Session", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSessionAuthAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSessionCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "userAddr", + "type": "address" + } + ], + "name": "getSessionCountByAddress", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "userAddr", + "type": "address" + } + ], + "name": "getSessionIdAndNameAndMetadataByAddress", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "string[]", + "name": "", + "type": "string[]" + }, + { + "internalType": "string[]", + "name": "", + "type": "string[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "userAddr", + "type": "address" + } + ], + "name": "getSessionIds", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSessionQueueAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSessionStatsAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "userAddr", + "type": "address" + } + ], + "name": "getSessionsByAddress", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "sid", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "internalType": "enum SessionV2.SessionState", + "name": "state", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "createdAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address[]", + "name": "ephemeralNodes", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "dedicatedNodes", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "mode", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "redundant", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "numOfValidatorNodes", + "type": "uint256" + }, + { + "internalType": "string[]", + "name": "routerMetadatas", + "type": "string[]" + }, + { + "internalType": "address[]", + "name": "routerAddresses", + "type": "address[]" + } + ], + "internalType": "struct SessionV2.Session[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "userAddr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "getSessionsByAddressByPage", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "sid", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "internalType": "enum SessionV2.SessionState", + "name": "state", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "createdAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address[]", + "name": "ephemeralNodes", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "dedicatedNodes", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "mode", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "redundant", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "numOfValidatorNodes", + "type": "uint256" + }, + { + "internalType": "string[]", + "name": "routerMetadatas", + "type": "string[]" + }, + { + "internalType": "address[]", + "name": "routerAddresses", + "type": "address[]" + } + ], + "internalType": "struct SessionV2.Session[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "userAddr", + "type": "address" + } + ], + "name": "getSessionsIdsByAddress", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskRequset", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requestDate", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "completed", + "type": "bool" + }, + { + "internalType": "string", + "name": "data", + "type": "string" + } + ], + "internalType": "struct SessionV2.TaskRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" + } + ], + "name": "getTaskResults", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "string[]", + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getUserSessions", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "nodeAddress", + "type": "address" + } + ], + "name": "isNodeAssignedToAnySession", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isNodeInExecutionMode", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isNodeInSessions", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "isOracleNode", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "nodeIdToAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nodeToSessionId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "nodeId", + "type": "bytes32" + } + ], + "name": "releaseByNodeId", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "nodeAddress", + "type": "address" + } + ], + "name": "removeDedicatedNode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "routerAddress", + "type": "address" + } + ], + "name": "removeRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + } + ], + "name": "resetAllRouters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sessionBalances", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sessionTaskRequestCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "sessions", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "sid", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "internalType": "enum SessionV2.SessionState", + "name": "state", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "createdAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "mode", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "redundant", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "numOfValidatorNodes", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_aclContract", + "type": "address" + } + ], + "name": "setACL", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_iamContract", + "type": "address" + } + ], + "name": "setIAM", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodePool", + "type": "address" + } + ], + "name": "setNodePool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sessionAuth", + "type": "address" + } + ], + "name": "setSessionAuth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sessionQueueContrat", + "type": "address" + } + ], + "name": "setSessionQueue", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sessionStats", + "type": "address" + } + ], + "name": "setSessionStats", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "taskData", + "type": "string" + } + ], + "name": "storeTaskRequest", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requestDate", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "completed", + "type": "bool" + }, + { + "internalType": "string", + "name": "data", + "type": "string" + } + ], + "internalType": "struct SessionV2.TaskRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nodeType", + "type": "uint256" + }, + { + "internalType": "string", + "name": "taskData", + "type": "string" + }, + { + "internalType": "uint256", + "name": "promptType", + "type": "uint256" + }, + { + "internalType": "string", + "name": "promptTemplate", + "type": "string" + }, + { + "internalType": "uint256[]", + "name": "llmParams", + "type": "uint256[]" + }, + { + "internalType": "string", + "name": "clientReference", + "type": "string" + } + ], + "name": "submit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "taskCountPerNode", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tasksBeforeRelease", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxNumOfNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "redundant", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "numOfValidatorNodes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mode", + "type": "uint256" + } + ], + "name": "update", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "updateOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "sessionId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "routerAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "newRouterInfo", + "type": "string" + } + ], + "name": "updateRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "userSessions", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/apps/AgentAuditor/backend/__init__.py b/apps/AgentAuditor/backend/__init__.py new file mode 100644 index 0000000..2b1bffb --- /dev/null +++ b/apps/AgentAuditor/backend/__init__.py @@ -0,0 +1,4 @@ +""" +Backend package initialization +""" +__version__ = "1.0.0" diff --git a/apps/AgentAuditor/backend/base.py b/apps/AgentAuditor/backend/base.py new file mode 100644 index 0000000..9b4c354 --- /dev/null +++ b/apps/AgentAuditor/backend/base.py @@ -0,0 +1,6 @@ +""" +SQLAlchemy declarative base +""" +from sqlalchemy.orm import declarative_base + +Base = declarative_base() \ No newline at end of file diff --git a/apps/AgentAuditor/backend/config.py b/apps/AgentAuditor/backend/config.py new file mode 100644 index 0000000..a6c633d --- /dev/null +++ b/apps/AgentAuditor/backend/config.py @@ -0,0 +1,62 @@ +""" +Configuration module for Cortensor Agent Auditor +""" +from pydantic_settings import BaseSettings +from typing import Optional + + +class Settings(BaseSettings): + """Application settings loaded from environment variables""" + + # Cortensor Router Configuration + cortensor_api_url: str = "http://127.0.0.1:5010" + cortensor_api_key: str = "" + cortensor_session_id: int = 0 + + # Blockchain Configuration + arbitrum_sepolia_rpc_url: str = "https://sepolia-rollup.arbitrum.io/rpc" + private_key: str = "" + session_v2_address: str = "" + session_queue_v2_address: str = "" + + # Cortensor Model Configuration + cortensor_model_general: str = "cts-llm-2" + cortensor_model_reasoning: str = "cts-llm-14" + + # PoI Configuration + poi_redundancy: int = 3 + poi_num_nodes: int = 3 # Added + poi_similarity_threshold: float = 0.6 + + # PoUW Configuration + pouw_num_validators: int = 1 + pouw_quality_threshold: float = 0.6 # Added + pouw_consensus_threshold: float = 0.7 # Added + pouw_confidence_weight_poi: float = 0.5 + pouw_confidence_weight_pouw: float = 0.5 + + # Database + database_url: str = "sqlite:///./agent_auditor.db" + + # IPFS Configuration + ipfs_provider: str = "pinata" + pinata_api_key: Optional[str] = None + pinata_secret_key: Optional[str] = None + + # API Configuration + api_host: str = "0.0.0.0" + api_port: int = 8000 + api_key: str = "" + secret_key: str = "change-this-secret-key" + cors_origins: str = "http://localhost:3000,http://localhost:5173" + + # Logging + log_level: str = "INFO" + + class Config: + env_file = ".env" + extra = "ignore" + + +# Global settings instance +settings = Settings() diff --git a/apps/AgentAuditor/backend/cortensor_client.py b/apps/AgentAuditor/backend/cortensor_client.py new file mode 100644 index 0000000..29b5207 --- /dev/null +++ b/apps/AgentAuditor/backend/cortensor_client.py @@ -0,0 +1,263 @@ +""" +Cortensor REST API Client +Communicates with local Cortensor Router node via REST API +""" +import logging +import time +import requests +from typing import Dict, Any, List, Optional + +from backend.config import settings + +logger = logging.getLogger(__name__) + + +class CortensorClient: + """ + Client for interacting with Cortensor Router REST API + """ + + def __init__(self): + self.base_url = settings.cortensor_api_url.rstrip('/') + self.api_key = settings.api_key + self.session_id = settings.cortensor_session_id + + self.headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.api_key}", + } + + logger.info(f"Cortensor Client initialized: {self.base_url}") + logger.info(f"Session ID: {self.session_id}") + logger.info(f"API Key: {'configured' if self.api_key else 'missing'}") + + def health_check(self) -> bool: + """Check if router is reachable""" + try: + response = requests.get( + f"{self.base_url}/api/v1/completions", + headers=self.headers, + timeout=5 + ) + return response.status_code in [200, 405] + except Exception as e: + logger.error(f"Router health check failed: {e}") + return False + + def submit_completion( + self, + prompt: str, + model: str = None, + max_tokens: int = 2048, + temperature: float = 0.7, + top_p: float = 0.95, + timeout: int = 300 # Increased timeout for async processing + ) -> Dict[str, Any]: + """Submit a completion request to Cortensor network""" + + if model is None: + model = settings.cortensor_model_general + + payload = { + "model": model, + "prompt": prompt, + "max_tokens": max_tokens, + "temperature": temperature, + "top_p": top_p, + "session_id": self.session_id + } + + try: + logger.info(f"Submitting completion to session {self.session_id}") + logger.info(f"Prompt: {prompt[:100]}...") + + response = requests.post( + f"{self.base_url}/api/v1/completions", + headers=self.headers, + json=payload, + timeout=timeout # Longer timeout for miner response + ) + + logger.info(f"Response status: {response.status_code}") + + if response.status_code == 401: + logger.error(f"Authentication failed: {response.text}") + raise PermissionError("Cortensor API authentication failed") + + if response.status_code == 404: + logger.error("Endpoint not found") + raise ValueError("Cortensor endpoint not found") + + response.raise_for_status() + result = response.json() + + # Extract text from response (handle different formats) + text = "" + if "choices" in result and len(result["choices"]) > 0: + text = result["choices"][0].get("text", "") + elif "text" in result: + text = result["text"] + elif "output" in result: + text = result["output"] + + logger.info(f"Completion received: {len(text)} chars") + logger.info(f"Response preview: {text[:200]}...") + + return { + "text": text, + "model": result.get("model", model), + "session_id": result.get("session_id", self.session_id), + "task_id": result.get("task_id"), + "usage": result.get("usage", {}), + "raw": result + } + + except requests.exceptions.Timeout: + logger.error(f"Request timed out after {timeout}s") + raise TimeoutError(f"Request timed out after {timeout} seconds") + + except requests.exceptions.RequestException as e: + logger.error(f"Request failed: {e}") + raise + + def submit_completion_redundant( + self, + prompt: str, + num_nodes: int = 3, + model: str = None, + max_tokens: int = 2048, + temperature: float = 0.7 + ) -> List[Dict[str, Any]]: + """Submit completion to multiple nodes (for PoI)""" + + if model is None: + model = settings.cortensor_model_general + + # First try the redundant endpoint + try: + logger.info(f"Submitting redundant completion ({num_nodes} nodes)") + + payload = { + "model": model, + "prompt": prompt, + "max_tokens": max_tokens, + "temperature": temperature, + "top_p": 0.95, + "session_id": self.session_id, + "n": num_nodes # Request multiple completions + } + + response = requests.post( + f"{self.base_url}/api/v1/completions", + headers=self.headers, + json=payload, + timeout=300 + ) + + if response.status_code == 200: + result = response.json() + + # Handle multiple choices + if "choices" in result: + results = [] + for i, choice in enumerate(result["choices"]): + results.append({ + "text": choice.get("text", ""), + "node_index": i, + "model": result.get("model", model), + "raw": choice + }) + + if len(results) >= 2: + logger.info(f"Got {len(results)} responses from redundant request") + return results + + logger.warning("Redundant endpoint didn't return enough results, using fallback") + + except Exception as e: + logger.warning(f"Redundant endpoint failed: {e}, using fallback") + + # Fallback: sequential requests + return self._fallback_sequential(prompt, num_nodes, model, max_tokens, temperature) + + def _fallback_sequential( + self, + prompt: str, + num_nodes: int, + model: str, + max_tokens: int, + temperature: float + ) -> List[Dict[str, Any]]: + """Fallback: make sequential requests""" + logger.info(f"Using fallback: {num_nodes} sequential requests") + + results = [] + for i in range(num_nodes): + try: + logger.info(f"Request {i+1}/{num_nodes}...") + result = self.submit_completion( + prompt=prompt, + model=model, + max_tokens=max_tokens, + temperature=temperature, + timeout=300 + ) + result['node_index'] = i + results.append(result) + logger.info(f"Response {i+1}/{num_nodes} received: {len(result.get('text', ''))} chars") + + # Small delay between requests + if i < num_nodes - 1: + time.sleep(1) + + except Exception as e: + logger.warning(f"Request {i+1} failed: {e}") + + return results + + def submit_validation( + self, + prompt: str, + num_validators: int = 1, + model: str = None + ) -> List[Dict[str, Any]]: + """Submit validation requests (for PoUW)""" + + if model is None: + model = settings.cortensor_model_reasoning + + logger.info(f"Submitting {num_validators} validation requests") + + results = [] + for i in range(num_validators): + try: + result = self.submit_completion( + prompt=prompt, + model=model, + max_tokens=100, + temperature=0.3, + timeout=300 + ) + result['validator_index'] = i + results.append(result) + logger.info(f"Validation {i+1}/{num_validators} received") + + if i < num_validators - 1: + time.sleep(1) + + except Exception as e: + logger.warning(f"Validation {i+1} failed: {e}") + + return results + + def get_session_info(self) -> Dict[str, Any]: + """Get session info""" + return { + "session_id": self.session_id, + "api_url": self.base_url, + "status": "active" + } + + +# Global client instance +cortensor_client = CortensorClient() \ No newline at end of file diff --git a/apps/AgentAuditor/backend/database.py b/apps/AgentAuditor/backend/database.py new file mode 100644 index 0000000..18ce2f3 --- /dev/null +++ b/apps/AgentAuditor/backend/database.py @@ -0,0 +1,81 @@ +""" +Database models and session management +""" +import os +from contextlib import contextmanager +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, Session +from typing import Generator +import logging + +from backend.config import settings +from backend.base import Base # Changed from backend.models + +logger = logging.getLogger(__name__) + +# Create database directory if needed +db_path = settings.database_url.replace("sqlite:///", "") +if db_path.startswith("./"): + db_dir = os.path.dirname(db_path) + if db_dir and not os.path.exists(db_dir): + os.makedirs(db_dir, exist_ok=True) + +# Create engine +engine = create_engine( + settings.database_url, + connect_args={"check_same_thread": False} if "sqlite" in settings.database_url else {}, + echo=False +) + +# Create session factory +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + + +def init_db(): + """Initialize database tables""" + try: + # Import models to register them with Base + from backend.models import Agent, Audit # noqa + + Base.metadata.create_all(bind=engine) + print("Database tables created successfully") + logger.info("Database tables created successfully") + except Exception as e: + logger.error(f"Database initialization failed: {e}") + raise + + +def get_db_session() -> Generator[Session, None, None]: + """ + FastAPI dependency for database sessions + + Usage: + @app.get("/example") + def example(db: Session = Depends(get_db_session)): + ... + """ + db = SessionLocal() + try: + yield db + finally: + db.close() + + +@contextmanager +def get_db_context(): + """ + Context manager for manual database sessions + + Usage: + with get_db_context() as db: + ... + """ + db = SessionLocal() + try: + yield db + db.commit() + except Exception: + db.rollback() + raise + finally: + db.close() diff --git a/apps/AgentAuditor/backend/engines/__init__.py b/apps/AgentAuditor/backend/engines/__init__.py new file mode 100644 index 0000000..c18a8c2 --- /dev/null +++ b/apps/AgentAuditor/backend/engines/__init__.py @@ -0,0 +1,9 @@ +""" +Engines package initialization +""" +from backend.engines.poi_engine import PoIEngine +from backend.engines.pouw_engine import PoUWEngine +from backend.engines.evidence_generator import EvidenceBundleGenerator +from backend.engines.ipfs_client import ipfs_client + +__all__ = ['PoIEngine', 'PoUWEngine', 'EvidenceBundleGenerator', 'ipfs_client'] diff --git a/apps/AgentAuditor/backend/engines/evidence_generator.py b/apps/AgentAuditor/backend/engines/evidence_generator.py new file mode 100644 index 0000000..884f2cd --- /dev/null +++ b/apps/AgentAuditor/backend/engines/evidence_generator.py @@ -0,0 +1,257 @@ +""" +Evidence Bundle Generator +Creates verifiable, immutable audit records and stores them on IPFS +""" +import json +import hashlib +import time +from typing import Dict, Any +from eth_account.messages import encode_defunct +import logging +import numpy as np + +from backend.config import settings +from backend.web3_client import web3_client +from backend.engines.ipfs_client import ipfs_client + +logger = logging.getLogger(__name__) + + +class EvidenceBundleGenerator: + """ + Generates evidence bundles containing all audit data + Signs bundles for verification and uploads to IPFS + """ + + def __init__(self): + self.account = web3_client.account + logger.info("Evidence Bundle Generator initialized") + + def generate_bundle( + self, + audit_id: str, + agent_id: str, + task_description: str, + task_input: str, + poi_result: Dict[str, Any], + pouw_result: Dict[str, Any], + final_confidence: float + ) -> Dict[str, Any]: + """ + Generate a complete evidence bundle + + Args: + audit_id: Unique audit identifier + agent_id: Agent being audited + task_description: Description of the task + task_input: Original input + poi_result: PoI validation results + pouw_result: PoUW validation results + final_confidence: Final confidence score + + Returns: + Complete evidence bundle dictionary + """ + logger.info(f"Generating evidence bundle for audit {audit_id}") + + # Convert similarity matrix to serializable format + similarity_matrix = poi_result.get('similarity_matrix') + if similarity_matrix is not None: + if isinstance(similarity_matrix, np.ndarray): + similarity_matrix = similarity_matrix.tolist() + elif isinstance(similarity_matrix, list): + # Convert any numpy values in the list + similarity_matrix = [[float(x) if isinstance(x, (np.floating, np.integer)) else x + for x in row] for row in similarity_matrix] + + # Create bundle structure with JSON-serializable data + bundle = { + "version": "1.0", + "audit_id": audit_id, + "agent_id": agent_id, + "timestamp": int(time.time()), + "task": { + "description": task_description, + "input": task_input, + "category": "general" + }, + "poi_validation": { + "session_id": self._to_json_safe(poi_result.get('session_id')), + "task_id": self._to_json_safe(poi_result.get('task_id')), + "similarity_score": float(poi_result.get('similarity_score', 0.0)), + "threshold": float(poi_result.get('similarity_threshold', 0.0)), + "passed": bool(poi_result.get('passed', False)), + "num_nodes": int(poi_result.get('num_nodes', 0)), + "consensus_output": str(poi_result.get('consensus_output', '')), + "outliers": [str(x) for x in poi_result.get('outliers', [])], + "similarity_matrix": similarity_matrix + }, + "pouw_validation": { + "session_id": self._to_json_safe(pouw_result.get('session_id')), + "overall_score": float(pouw_result.get('overall_score', 0.0)), + "overall_score_raw": float(pouw_result.get('overall_score_raw', 0.0)), + "passed": bool(pouw_result.get('passed', False)), + "num_validators": int(pouw_result.get('num_validators', 0)), + "criterion_scores": self._convert_criterion_scores(pouw_result.get('criterion_scores', {})) + }, + "final_assessment": { + "confidence_score": float(final_confidence), + "poi_weight": float(settings.pouw_confidence_weight_poi), + "pouw_weight": float(settings.pouw_confidence_weight_pouw), + "calculation": f"{settings.pouw_confidence_weight_poi} * {poi_result.get('similarity_score', 0):.3f} + {settings.pouw_confidence_weight_pouw} * {pouw_result.get('overall_score', 0):.3f}" + }, + "metadata": { + "auditor_address": self.account.address, + "network": "arbitrum-sepolia", + "cortensor_version": "v2" + } + } + + # Generate hash of bundle + bundle_json = json.dumps(bundle, sort_keys=True) + bundle_hash = hashlib.sha256(bundle_json.encode()).hexdigest() + bundle['bundle_hash'] = bundle_hash + + # Sign the bundle + signature = self._sign_bundle(bundle_hash) + bundle['signature'] = signature + + # Upload to IPFS if configured + ipfs_cid = None + if settings.pinata_api_key: + try: + ipfs_cid = ipfs_client.upload_bundle(bundle) + logger.info(f"Evidence uploaded to IPFS: {ipfs_cid}") + except Exception as e: + logger.warning(f"IPFS upload failed: {e}") + else: + logger.info("IPFS not configured, skipping upload") + + bundle['ipfs_cid'] = ipfs_cid + + logger.info(f"Bundle generated: hash={bundle_hash[:16]}...") + + return bundle + + def _to_json_safe(self, value: Any) -> Any: + """Convert value to JSON-safe type""" + if value is None: + return None + if isinstance(value, (np.integer, np.floating)): + return float(value) + if isinstance(value, np.ndarray): + return value.tolist() + if isinstance(value, (bool, np.bool_)): + return bool(value) + return value + + def _convert_criterion_scores(self, scores: Dict[str, Any]) -> Dict[str, Any]: + """Convert criterion scores to JSON-serializable format""" + result = {} + for key, value in scores.items(): + if isinstance(value, dict): + result[key] = { + "scores": [float(s) for s in value.get("scores", [])], + "average": float(value.get("average", 0.0)), + "min": float(value.get("min", 0.0)), + "max": float(value.get("max", 0.0)) + } + else: + result[key] = float(value) if isinstance(value, (int, float, np.integer, np.floating)) else value + return result + + def _sign_bundle(self, bundle_hash: str) -> str: + """ + Sign the bundle hash with auditor's private key + + Args: + bundle_hash: SHA-256 hash of the bundle + + Returns: + Hex-encoded signature + """ + # Create message to sign + message = encode_defunct(text=bundle_hash) + + # Sign with private key + signed_message = web3_client.w3.eth.account.sign_message( + message, + private_key=self.account.key + ) + + return signed_message.signature.hex() + + def verify_bundle(self, bundle: Dict[str, Any]) -> bool: + """ + Verify the authenticity of an evidence bundle + + Args: + bundle: Evidence bundle to verify + + Returns: + True if bundle is valid and signature matches + """ + try: + # Extract signature and hash + signature = bundle.get('signature') + claimed_hash = bundle.get('bundle_hash') + + if not signature or not claimed_hash: + return False + + # Recalculate hash + bundle_copy = bundle.copy() + bundle_copy.pop('signature', None) + bundle_copy.pop('bundle_hash', None) + bundle_copy.pop('ipfs_cid', None) # Don't include IPFS CID in hash verification + + bundle_json = json.dumps(bundle_copy, sort_keys=True) + actual_hash = hashlib.sha256(bundle_json.encode()).hexdigest() + + # Verify hash matches + if actual_hash != claimed_hash: + logger.warning("Bundle hash mismatch") + return False + + # Verify signature + message = encode_defunct(text=claimed_hash) + recovered_address = web3_client.w3.eth.account.recover_message( + message, + signature=signature + ) + + # Check if recovered address matches auditor + expected_address = bundle.get('metadata', {}).get('auditor_address') + + if recovered_address.lower() != expected_address.lower(): + logger.warning("Signature verification failed") + return False + + logger.info("Bundle verification successful") + return True + + except Exception as e: + logger.error(f"Bundle verification error: {e}") + return False + + def calculate_final_confidence( + self, + poi_similarity: float, + pouw_score: float + ) -> float: + """ + Calculate final confidence score as weighted average of PoI and PoUW + + Args: + poi_similarity: PoI similarity score (0-1) + pouw_score: PoUW overall score (0-1) + + Returns: + Final confidence score (0-1) + """ + confidence = ( + settings.pouw_confidence_weight_poi * poi_similarity + + settings.pouw_confidence_weight_pouw * pouw_score + ) + + return max(0.0, min(1.0, confidence)) diff --git a/apps/AgentAuditor/backend/engines/ipfs_client.py b/apps/AgentAuditor/backend/engines/ipfs_client.py new file mode 100644 index 0000000..e427ca3 --- /dev/null +++ b/apps/AgentAuditor/backend/engines/ipfs_client.py @@ -0,0 +1,150 @@ +""" +IPFS integration for storing evidence bundles +Supports Pinata and Web3.Storage providers +""" +import json +import requests +import logging +from typing import Dict, Any, Optional + +from backend.config import settings + +logger = logging.getLogger(__name__) + + +class IPFSClient: + """Client for uploading evidence bundles to IPFS""" + + def __init__(self): + self.provider = settings.ipfs_provider + logger.info(f"IPFS client initialized with provider: {self.provider}") + + def upload_bundle(self, bundle: Dict[str, Any]) -> str: + """ + Upload evidence bundle to IPFS + + Args: + bundle: Evidence bundle dictionary + + Returns: + IPFS hash (CID) + """ + if self.provider == "pinata": + return self._upload_pinata(bundle) + elif self.provider == "web3storage": + return self._upload_web3storage(bundle) + else: + raise ValueError(f"Unsupported IPFS provider: {self.provider}") + + def _upload_pinata(self, bundle: Dict[str, Any]) -> str: + """ + Upload to Pinata IPFS service + + Args: + bundle: Evidence bundle + + Returns: + IPFS hash + """ + url = "https://api.pinata.cloud/pinning/pinJSONToIPFS" + + headers = { + "pinata_api_key": settings.pinata_api_key, + "pinata_secret_api_key": settings.pinata_secret_key + } + + payload = { + "pinataContent": bundle, + "pinataMetadata": { + "name": f"cortensor-audit-{bundle['audit_id']}", + "keyvalues": { + "audit_id": bundle['audit_id'], + "agent_id": bundle['agent_id'], + "confidence": str(bundle['final_assessment']['confidence_score']) + } + } + } + + try: + response = requests.post(url, json=payload, headers=headers) + response.raise_for_status() + + result = response.json() + ipfs_hash = result['IpfsHash'] + + logger.info(f"Bundle uploaded to Pinata: {ipfs_hash}") + return ipfs_hash + + except Exception as e: + logger.error(f"Failed to upload to Pinata: {e}") + raise + + def _upload_web3storage(self, bundle: Dict[str, Any]) -> str: + """ + Upload to Web3.Storage IPFS service + + Args: + bundle: Evidence bundle + + Returns: + IPFS hash + """ + url = "https://api.web3.storage/upload" + + headers = { + "Authorization": f"Bearer {settings.web3_storage_token}", + "Content-Type": "application/json" + } + + try: + bundle_json = json.dumps(bundle) + + response = requests.post(url, data=bundle_json, headers=headers) + response.raise_for_status() + + result = response.json() + ipfs_hash = result['cid'] + + logger.info(f"Bundle uploaded to Web3.Storage: {ipfs_hash}") + return ipfs_hash + + except Exception as e: + logger.error(f"Failed to upload to Web3.Storage: {e}") + raise + + def retrieve_bundle(self, ipfs_hash: str) -> Optional[Dict[str, Any]]: + """ + Retrieve evidence bundle from IPFS + + Args: + ipfs_hash: IPFS CID + + Returns: + Bundle dictionary or None if not found + """ + # Use public IPFS gateway + gateways = [ + f"https://gateway.pinata.cloud/ipfs/{ipfs_hash}", + f"https://ipfs.io/ipfs/{ipfs_hash}", + f"https://cloudflare-ipfs.com/ipfs/{ipfs_hash}" + ] + + for gateway in gateways: + try: + response = requests.get(gateway, timeout=10) + response.raise_for_status() + + bundle = response.json() + logger.info(f"Bundle retrieved from IPFS: {ipfs_hash}") + return bundle + + except Exception as e: + logger.warning(f"Failed to retrieve from {gateway}: {e}") + continue + + logger.error(f"Failed to retrieve bundle from all gateways: {ipfs_hash}") + return None + + +# Global IPFS client instance +ipfs_client = IPFSClient() diff --git a/apps/AgentAuditor/backend/engines/poi_engine.py b/apps/AgentAuditor/backend/engines/poi_engine.py new file mode 100644 index 0000000..b3b6f93 --- /dev/null +++ b/apps/AgentAuditor/backend/engines/poi_engine.py @@ -0,0 +1,162 @@ +""" +Proof of Inference (PoI) Engine +Validates output consistency across redundant nodes via embedding similarity +""" +import logging +import time +import numpy as np +from typing import List, Dict, Any, Tuple +from sentence_transformers import SentenceTransformer +from sklearn.metrics.pairwise import cosine_similarity + +from backend.config import settings +from backend.cortensor_client import cortensor_client + +logger = logging.getLogger(__name__) + + +class PoIEngine: + """ + Proof of Inference Engine + + Creates sessions with redundant nodes and validates consistency + via embedding similarity calculations. + """ + + def __init__(self): + # Load sentence transformer model for embeddings + logger.info("Loading embedding model...") + self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2') + logger.info("PoI Engine initialized with all-MiniLM-L6-v2") + + def submit_and_validate( + self, + prompt: str, + num_nodes: int = None, + similarity_threshold: float = None + ) -> Dict[str, Any]: + """ + Submit prompt to redundant nodes and validate consistency + + Args: + prompt: The input prompt + num_nodes: Number of redundant nodes (default from config) + similarity_threshold: Minimum similarity for consensus + + Returns: + PoI validation result with scores and consensus + """ + if num_nodes is None: + num_nodes = settings.poi_redundancy + if similarity_threshold is None: + similarity_threshold = settings.poi_similarity_threshold + + logger.info(f"PoI validation: {num_nodes} nodes, threshold={similarity_threshold}") + + start_time = time.time() + + # Step 1: Get responses from redundant nodes + responses = cortensor_client.submit_completion_redundant( + prompt=prompt, + num_nodes=num_nodes, + model=settings.cortensor_model_general + ) + + if len(responses) < 2: + logger.warning(f"Only got {len(responses)} responses, need at least 2") + if len(responses) == 1: + return { + 'passed': True, + 'similarity_score': 1.0, + 'consensus_output': responses[0].get('text', ''), + 'num_nodes': 1, + 'outputs': [responses[0].get('text', '')], + 'outliers': [], + 'embeddings': [], + 'similarity_matrix': [[1.0]], + 'processing_time': time.time() - start_time + } + raise ValueError("No responses received from nodes") + + # Step 2: Extract output texts + outputs = [r.get('text', r.get('output', '')) for r in responses] + + # Step 3: Generate embeddings + logger.info("Generating embeddings...") + embeddings = self.embedding_model.encode(outputs, convert_to_numpy=True) + + # Step 4: Calculate similarity matrix + similarity_matrix = cosine_similarity(embeddings) + + # Step 5: Calculate average pairwise similarity + n = len(similarity_matrix) + if n > 1: + # Sum all similarities except diagonal, divide by number of pairs + avg_similarity = (np.sum(similarity_matrix) - n) / (n * (n - 1)) + else: + avg_similarity = 1.0 + + # Step 6: Detect consensus and outliers + consensus_output, outliers = self._detect_consensus( + outputs, embeddings, similarity_matrix, similarity_threshold + ) + + # Step 7: Determine pass/fail + passed = avg_similarity >= similarity_threshold + + result = { + 'passed': passed, + 'similarity_score': float(avg_similarity), + 'similarity_threshold': similarity_threshold, + 'consensus_output': consensus_output, + 'num_nodes': len(outputs), + 'outputs': outputs, + 'outliers': outliers, + 'embeddings': embeddings.tolist(), + 'similarity_matrix': similarity_matrix.tolist(), + 'processing_time': time.time() - start_time + } + + logger.info(f"PoI complete: similarity={avg_similarity:.3f}, passed={passed}") + + return result + + def _detect_consensus( + self, + outputs: List[str], + embeddings: np.ndarray, + similarity_matrix: np.ndarray, + threshold: float + ) -> Tuple[str, List[int]]: + """ + Detect consensus output and identify outliers + + Returns: + Tuple of (consensus_output, outlier_indices) + """ + n = len(outputs) + + # Calculate average similarity for each output to all others + avg_similarities = [] + for i in range(n): + # Get similarities to all other outputs (excluding self) + similarities = [similarity_matrix[i][j] for j in range(n) if i != j] + avg_sim = np.mean(similarities) if similarities else 0.0 + avg_similarities.append(avg_sim) + + # Output with highest average similarity is the consensus + consensus_idx = int(np.argmax(avg_similarities)) + consensus_output = outputs[consensus_idx] + + # Identify outliers (below threshold) + outliers = [i for i, sim in enumerate(avg_similarities) if sim < threshold] + + return consensus_output, outliers + + def calculate_poi_score(self, similarity_score: float) -> float: + """Convert similarity (0-1) to confidence score (0-1)""" + return similarity_score + + +# Global PoI engine instance +poi_engine = PoIEngine() diff --git a/apps/AgentAuditor/backend/engines/pouw_engine.py b/apps/AgentAuditor/backend/engines/pouw_engine.py new file mode 100644 index 0000000..a17e8f1 --- /dev/null +++ b/apps/AgentAuditor/backend/engines/pouw_engine.py @@ -0,0 +1,205 @@ +""" +Proof of Useful Work (PoUW) Engine +Validates output quality through multi-node consensus +""" +import logging +import re +from typing import Dict, Any, List, Optional +from dataclasses import dataclass + +from backend.cortensor_client import cortensor_client +from backend.config import settings + +logger = logging.getLogger(__name__) + + +@dataclass +class PoUWResult: + """Result of PoUW validation""" + is_valid: bool + quality_score: float # 0.0 to 1.0 + validator_scores: List[float] + consensus_reached: bool + validation_details: Dict[str, Any] + + +class PoUWEngine: + """ + Proof of Useful Work validation engine + Uses multiple validator nodes to assess output quality + """ + + def __init__(self): + self.num_validators = settings.pouw_num_validators + self.quality_threshold = settings.pouw_quality_threshold + self.consensus_threshold = settings.pouw_consensus_threshold + + def validate( + self, + task_description: str, + task_input: str, + agent_output: str, + num_validators: int = None + ) -> PoUWResult: + """ + Validate agent output quality using multiple validators + """ + if num_validators is None: + num_validators = self.num_validators + + logger.info(f"PoUW validation with {num_validators} validators") + + if not agent_output or len(agent_output.strip()) == 0: + logger.warning("Empty agent output - returning failed validation") + return PoUWResult( + is_valid=False, + quality_score=0.0, + validator_scores=[], + consensus_reached=False, + validation_details={"error": "Empty output"} + ) + + # Define validation criteria + criteria = [ + ("CORRECTNESS", "Is the output factually correct and accurate?"), + ("COMPLETENESS", "Does the output fully address the task requirements?"), + ("CLARITY", "Is the output clear and well-structured?"), + ] + + all_scores = [] + criteria_scores = {} + + for criterion_name, criterion_desc in criteria: + scores = self._validate_criterion( + task_description=task_description, + task_input=task_input, + agent_output=agent_output, + criterion_name=criterion_name, + criterion_desc=criterion_desc, + num_validators=num_validators + ) + + if scores: + criteria_scores[criterion_name] = { + "scores": scores, + "average": sum(scores) / len(scores), + "min": min(scores), + "max": max(scores) + } + all_scores.extend(scores) + + if not all_scores: + logger.warning("No validation scores received") + return PoUWResult( + is_valid=False, + quality_score=0.0, + validator_scores=[], + consensus_reached=False, + validation_details={"error": "No validator responses"} + ) + + # Calculate overall quality score + quality_score = sum(all_scores) / len(all_scores) / 10.0 # Normalize to 0-1 + + # Check consensus (are validators in agreement?) + score_variance = self._calculate_variance(all_scores) + consensus_reached = score_variance < 2.0 # Low variance = consensus + + # Determine validity + is_valid = quality_score >= self.quality_threshold and consensus_reached + + logger.info(f"PoUW result: score={quality_score:.2f}, consensus={consensus_reached}, valid={is_valid}") + + return PoUWResult( + is_valid=is_valid, + quality_score=quality_score, + validator_scores=all_scores, + consensus_reached=consensus_reached, + validation_details={ + "criteria_scores": criteria_scores, + "score_variance": score_variance, + "num_validators": num_validators + } + ) + + def _validate_criterion( + self, + task_description: str, + task_input: str, + agent_output: str, + criterion_name: str, + criterion_desc: str, + num_validators: int + ) -> List[float]: + """Validate a single criterion using multiple validators""" + + # Truncate output if too long + output_preview = agent_output[:500] + "..." if len(agent_output) > 500 else agent_output + + prompt = f"""Rate the {criterion_name} of this AI output on a scale of 1-10. + +Task: {task_description} +Input: {task_input} +Output: {output_preview} + +Criteria: {criterion_desc} + +Respond with ONLY a number from 1-10:""" + + scores = [] + + for i in range(num_validators): + try: + # Use regular completions endpoint (not validations) + result = cortensor_client.submit_completion( + prompt=prompt, + model=settings.cortensor_model_reasoning, + max_tokens=50, + temperature=0.3, + timeout=180 + ) + + text = result.get("text", "").strip() + score = self._extract_score(text) + + if score is not None: + scores.append(score) + logger.debug(f"Validator {i+1} score for {criterion_name}: {score}") + else: + logger.warning(f"Validator {i+1} returned invalid score: {text}") + + except Exception as e: + logger.warning(f"Validator {i+1} failed for {criterion_name}: {e}") + + return scores + + def _extract_score(self, text: str) -> Optional[float]: + """Extract numeric score from validator response""" + # Try to find a number in the response + numbers = re.findall(r'\b([1-9]|10)\b', text) + if numbers: + return float(numbers[0]) + + # Try to parse as float + try: + text_clean = text.strip().split()[0] if text.strip() else "" + score = float(text_clean) + if 1 <= score <= 10: + return score + except (ValueError, IndexError): + pass + + return None + + def _calculate_variance(self, scores: List[float]) -> float: + """Calculate variance of scores""" + if len(scores) < 2: + return 0.0 + + mean = sum(scores) / len(scores) + variance = sum((s - mean) ** 2 for s in scores) / len(scores) + return variance + + +# Global engine instance +pouw_engine = PoUWEngine() diff --git a/apps/AgentAuditor/backend/main.py b/apps/AgentAuditor/backend/main.py new file mode 100644 index 0000000..8afd05c --- /dev/null +++ b/apps/AgentAuditor/backend/main.py @@ -0,0 +1,443 @@ +""" +FastAPI application - REST API for Cortensor Agent Auditor +""" +import logging +from datetime import datetime +from fastapi import FastAPI, HTTPException, Depends +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel, Field +from typing import Optional, List +from sqlalchemy.orm import Session +from sqlalchemy import func, desc + +from backend.config import settings +from backend.database import init_db, get_db_session +from backend.orchestrator import orchestrator +from backend.models import Agent, Audit + +# Configure logging +logging.basicConfig( + level=getattr(logging, settings.log_level, "INFO"), + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +# Create FastAPI app +app = FastAPI( + title="Cortensor Agent Auditor API", + description="Trust & verification layer for AI agents using PoI and PoUW", + version="1.0.0", + docs_url="/docs", + redoc_url="/redoc" +) + +# CORS middleware +origins = settings.cors_origins.split(",") if settings.cors_origins else ["*"] +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +# ==================== REQUEST/RESPONSE MODELS ==================== +class AuditRequest(BaseModel): + agent_id: str = Field(..., description="Unique identifier for the agent", min_length=1) + agent_name: Optional[str] = Field(None, description="Human-readable agent name") + task_description: str = Field(..., description="Description of the task", min_length=1) + task_input: str = Field(..., description="The input/prompt for the agent", min_length=1) + category: Optional[str] = Field("general", description="Task category") + + +class AuditResponse(BaseModel): + audit_id: str + agent_id: str + status: str + confidence_score: Optional[float] = None + poi_similarity: Optional[float] = None + pouw_mean_score: Optional[float] = None + ipfs_hash: Optional[str] = None + timestamp: Optional[str] = None + error: Optional[str] = None + + +class AgentStats(BaseModel): + agent_id: str + agent_name: Optional[str] = None + total_audits: int = 0 + avg_confidence: float = 0.0 + avg_poi_similarity: float = 0.0 + avg_pouw_score: float = 0.0 + last_audit_time: Optional[str] = None + + +class DashboardStats(BaseModel): + total_audits: int + avg_confidence: float + successful_audits: int + failed_audits: int + recent_audits: List[dict] = [] + + +class AuditListItem(BaseModel): + audit_id: str + agent_id: str + agent_name: Optional[str] = None + timestamp: str + status: str + confidence_score: float = 0.0 + poi_similarity: float = 0.0 + pouw_score: float = 0.0 # Changed from pouw_mean_score + ipfs_hash: Optional[str] = None + + +class AuditListResponse(BaseModel): + total: int + audits: List[AuditListItem] +# ===================================================== + + +# ==================== STARTUP/SHUTDOWN ==================== +@app.on_event("startup") +async def startup_event(): + """Initialize on startup""" + logger.info("Starting Cortensor Agent Auditor API...") + + try: + # Initialize database + init_db() + logger.info("โ Database initialized") + + # Log configuration + logger.info(f"๐ Cortensor Session: {settings.cortensor_session_id}") + logger.info(f"๐ง PoI threshold: {settings.poi_similarity_threshold}") + logger.info(f"๐ง PoUW validators: {settings.pouw_num_validators}") + + except Exception as e: + logger.error(f"โ Startup failed: {e}") + raise + + +@app.on_event("shutdown") +async def shutdown_event(): + """Cleanup on shutdown""" + logger.info("Shutting down Cortensor Agent Auditor API...") +# ===================================================== + + +# ==================== ROOT ROUTES ==================== +@app.get("/") +async def root(): + """Health check - root endpoint""" + return { + "service": "Cortensor Agent Auditor", + "version": "1.0.0", + "status": "running", + "docs": "/docs" + } + + +@app.get("/health") +async def health(db: Session = Depends(get_db_session)): + """Detailed health check""" + try: + # Test database connection + db.execute("SELECT 1") + db_status = "connected" + except Exception as e: + logger.error(f"Database health check failed: {e}") + db_status = "disconnected" + + return { + "status": "healthy" if db_status == "connected" else "degraded", + "database": db_status, + "cortensor_session": settings.cortensor_session_id, + "timestamp": datetime.utcnow().isoformat() + } +# ===================================================== + + +# ==================== DASHBOARD STATS ==================== +@app.get("/api/v1/stats", response_model=DashboardStats) +async def get_dashboard_stats(db: Session = Depends(get_db_session)): + """Get overall dashboard statistics""" + try: + total_audits = db.query(Audit).count() + successful_audits = db.query(Audit).filter(Audit.status == "completed").count() + failed_audits = db.query(Audit).filter(Audit.status == "failed").count() + + avg_confidence = db.query(func.avg(Audit.final_confidence))\ + .filter(Audit.status == "completed")\ + .scalar() or 0.0 + + # Get recent audits + recent = db.query(Audit)\ + .order_by(desc(Audit.timestamp))\ + .limit(5)\ + .all() + + recent_audits = [ + { + "audit_id": audit.audit_id, + "agent_id": audit.agent_id, + "timestamp": audit.timestamp.isoformat(), + "status": audit.status, + "confidence_score": audit.final_confidence + } + for audit in recent + ] + + return DashboardStats( + total_audits=total_audits, + avg_confidence=float(avg_confidence), + successful_audits=successful_audits, + failed_audits=failed_audits, + recent_audits=recent_audits + ) + + except Exception as e: + logger.error(f"Error fetching dashboard stats: {e}") + raise HTTPException(status_code=500, detail=str(e)) +# ===================================================== + + +# ==================== AGENTS ==================== +@app.get("/api/v1/agents", response_model=List[AgentStats]) +async def get_agents( + limit: int = 10, + offset: int = 0, + db: Session = Depends(get_db_session) +): + """Get list of agents with their statistics""" + try: + # Get agents with audit statistics + agents_query = db.query( + Agent.agent_id, + Agent.name, + func.count(Audit.audit_id).label('total_audits'), + func.avg(Audit.final_confidence).label('avg_confidence'), + func.avg(Audit.poi_similarity).label('avg_poi'), + func.avg(Audit.pouw_mean_score).label('avg_pouw'), + func.max(Audit.timestamp).label('last_audit') + ).outerjoin(Audit, Agent.agent_id == Audit.agent_id)\ + .group_by(Agent.agent_id, Agent.name)\ + .order_by(desc('total_audits'))\ + .limit(limit)\ + .offset(offset) + + results = agents_query.all() + + agents = [] + for row in results: + agents.append(AgentStats( + agent_id=row.agent_id, + agent_name=row.name, + total_audits=row.total_audits or 0, + avg_confidence=float(row.avg_confidence or 0.0), + avg_poi_similarity=float(row.avg_poi or 0.0), + avg_pouw_score=float(row.avg_pouw or 0.0), + last_audit_time=row.last_audit.isoformat() if row.last_audit else None + )) + + return agents + + except Exception as e: + logger.error(f"Error fetching agents: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.get("/api/v1/agents/{agent_id}", response_model=AgentStats) +async def get_agent(agent_id: str, db: Session = Depends(get_db_session)): + """Get detailed agent information and statistics""" + try: + # Get or create agent + agent = db.query(Agent).filter(Agent.agent_id == agent_id).first() + if not agent: + raise HTTPException(status_code=404, detail="Agent not found") + + # Get statistics + stats = db.query( + func.count(Audit.audit_id).label('total_audits'), + func.avg(Audit.final_confidence).label('avg_confidence'), + func.avg(Audit.poi_similarity).label('avg_poi'), + func.avg(Audit.pouw_mean_score).label('avg_pouw'), + func.max(Audit.timestamp).label('last_audit') + ).filter(Audit.agent_id == agent_id).first() + + return AgentStats( + agent_id=agent.agent_id, + agent_name=agent.name, + total_audits=stats.total_audits or 0, + avg_confidence=float(stats.avg_confidence or 0.0), + avg_poi_similarity=float(stats.avg_poi or 0.0), + avg_pouw_score=float(stats.avg_pouw or 0.0), + last_audit_time=stats.last_audit.isoformat() if stats.last_audit else None + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Error fetching agent {agent_id}: {e}") + raise HTTPException(status_code=500, detail=str(e)) +# ===================================================== + + +# ==================== AUDITS ==================== +@app.get("/api/v1/audits", response_model=AuditListResponse) +async def get_audits( + limit: int = 10, + offset: int = 0, + agent_id: Optional[str] = None, + status: Optional[str] = None, + db: Session = Depends(get_db_session) +): + """Get list of audits with optional filtering""" + try: + query = db.query(Audit) + + if agent_id: + query = query.filter(Audit.agent_id == agent_id) + + if status: + query = query.filter(Audit.status == status) + + total = query.count() + + audits = query.order_by(desc(Audit.timestamp))\ + .limit(limit)\ + .offset(offset)\ + .all() + + audit_items = [ + AuditListItem( + audit_id=audit.audit_id, + agent_id=audit.agent_id, + agent_name=audit.agent_name, + timestamp=audit.timestamp.isoformat(), + status=audit.status, + confidence_score=audit.final_confidence, + poi_similarity=audit.poi_similarity, + pouw_score=audit.pouw_mean_score, + ipfs_hash=audit.ipfs_cid + ) + for audit in audits + ] + + return AuditListResponse(total=total, audits=audit_items) + + except Exception as e: + logger.error(f"Error fetching audits: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.post("/api/v1/audit", response_model=AuditResponse) +async def create_audit( + request: AuditRequest, + db: Session = Depends(get_db_session) +): + """Submit a new audit request""" + logger.info(f"๐ Audit request for agent: {request.agent_id}") + + try: + # Run audit through orchestrator + result = await orchestrator.run_audit( + agent_id=request.agent_id, + agent_name=request.agent_name or request.agent_id, + task_description=request.task_description, + task_input=request.task_input, + category=request.category or "general" + ) + + # Create or update agent in database + agent = db.query(Agent).filter(Agent.agent_id == request.agent_id).first() + if not agent: + agent = Agent( + agent_id=request.agent_id, + name=request.agent_name or request.agent_id + ) + db.add(agent) + try: + db.commit() + except Exception as e: + logger.warning(f"Agent creation failed: {e}") + db.rollback() + + return AuditResponse( + audit_id=result.get("audit_id", ""), + agent_id=result.get("agent_id", request.agent_id), + status=result.get("status", "failed"), + confidence_score=result.get("confidence_score"), + poi_similarity=result.get("poi_similarity"), + pouw_mean_score=result.get("pouw_mean_score"), + ipfs_hash=result.get("ipfs_cid"), + timestamp=result.get("timestamp"), + error=result.get("error") + ) + + except Exception as e: + logger.error(f"โ Audit failed: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@app.get("/api/v1/audit/{audit_id}") +async def get_audit(audit_id: str, db: Session = Depends(get_db_session)): + """Get detailed audit information""" + audit = db.query(Audit).filter(Audit.audit_id == audit_id).first() + + if not audit: + raise HTTPException(status_code=404, detail="Audit not found") + + return { + "audit_id": audit.audit_id, + "agent_id": audit.agent_id, + "agent_name": audit.agent_name, + "timestamp": audit.timestamp.isoformat(), + "status": audit.status, + "confidence_score": audit.final_confidence, + "poi_similarity": audit.poi_similarity, + "pouw_score": audit.pouw_mean_score, + "ipfs_hash": audit.ipfs_cid, + "task_description": audit.task_description, + "task_input": audit.task_input, + "category": audit.category + } + + +@app.get("/api/v1/audit/{audit_id}/evidence") +async def get_audit_evidence(audit_id: str, db: Session = Depends(get_db_session)): + """Retrieve full evidence bundle from IPFS""" + audit = db.query(Audit).filter(Audit.audit_id == audit_id).first() + + if not audit: + raise HTTPException(status_code=404, detail="Audit not found") + + if not audit.ipfs_cid: + raise HTTPException(status_code=404, detail="Evidence not stored on IPFS") + + try: + # Retrieve from IPFS + from backend.engines.ipfs_client import ipfs_client + bundle = ipfs_client.retrieve_bundle(audit.ipfs_cid) + + if not bundle: + raise HTTPException(status_code=503, detail="Unable to retrieve evidence from IPFS") + + return bundle + + except Exception as e: + logger.error(f"IPFS retrieval failed: {e}") + raise HTTPException(status_code=503, detail=f"IPFS error: {str(e)}") +# ===================================================== + + +if __name__ == "__main__": + import uvicorn + uvicorn.run( + "backend.main:app", + host=settings.api_host, + port=settings.api_port, + reload=True, + log_level="info" + ) diff --git a/apps/AgentAuditor/backend/models.py b/apps/AgentAuditor/backend/models.py new file mode 100644 index 0000000..5b3d7bf --- /dev/null +++ b/apps/AgentAuditor/backend/models.py @@ -0,0 +1,53 @@ +""" +Database models for Agent Auditor +""" +from sqlalchemy import Column, Integer, String, Float, DateTime, Text, ForeignKey +from sqlalchemy.orm import relationship +from datetime import datetime + +from backend.base import Base # Changed from backend.database + + +class Agent(Base): + """Agent model - stores information about audited agents""" + __tablename__ = "agents" + + id = Column(Integer, primary_key=True, index=True) + agent_id = Column(String, unique=True, index=True, nullable=False) + name = Column(String, nullable=True) + created_at = Column(DateTime, default=datetime.utcnow) + + # Relationship to audits + audits = relationship("Audit", back_populates="agent") + + +class Audit(Base): + """Audit model - stores audit results""" + __tablename__ = "audits" + + id = Column(Integer, primary_key=True, index=True) + audit_id = Column(String, unique=True, index=True, nullable=False) + agent_id = Column(String, ForeignKey("agents.agent_id"), nullable=False) + agent_name = Column(String, nullable=True) + + # Task information + task_description = Column(Text, nullable=False) + task_input = Column(Text, nullable=False) + category = Column(String, default="general") + + # Audit results + status = Column(String, default="pending") # pending, running, completed, failed + final_confidence = Column(Float, nullable=True) + poi_similarity = Column(Float, nullable=True) + pouw_mean_score = Column(Float, nullable=True) + + # Evidence storage + ipfs_cid = Column(String, nullable=True) + evidence_hash = Column(String, nullable=True) + + # Timestamps + timestamp = Column(DateTime, default=datetime.utcnow, index=True) + completed_at = Column(DateTime, nullable=True) + + # Relationship to agent + agent = relationship("Agent", back_populates="audits") diff --git a/apps/AgentAuditor/backend/orchestrator.py b/apps/AgentAuditor/backend/orchestrator.py new file mode 100644 index 0000000..7203be2 --- /dev/null +++ b/apps/AgentAuditor/backend/orchestrator.py @@ -0,0 +1,295 @@ +""" +Audit Orchestrator +Coordinates the audit pipeline: PoI โ PoUW โ Evidence Generation +""" +import logging +import time +import secrets +from typing import Dict, Any, Optional, List +from datetime import datetime + +from backend.engines.poi_engine import poi_engine +from backend.engines.pouw_engine import pouw_engine +from backend.engines.evidence_generator import EvidenceBundleGenerator +from backend.config import settings +from backend.database import get_db_context +from backend.models import Agent, Audit + +logger = logging.getLogger(__name__) + + +class AuditOrchestrator: + """ + Orchestrates the complete audit pipeline + """ + + def __init__(self): + self.poi_engine = poi_engine + self.pouw_engine = pouw_engine + self.evidence_generator = EvidenceBundleGenerator() + + async def run_audit( + self, + agent_id: str, + agent_name: Optional[str], + task_description: str, + task_input: str, + category: str = "general" + ) -> Dict[str, Any]: + """ + Run complete audit pipeline + """ + audit_id = f"aud_{secrets.token_hex(6)}" + + logger.info(f"Starting audit {audit_id} for agent {agent_id}") + + start_time = time.time() + + try: + # Ensure agent exists in database + self._ensure_agent_exists(agent_id, agent_name) # REMOVED await + + # Step 1: Build the full prompt for the agent + full_prompt = self._build_agent_prompt(task_description, task_input) + logger.info(f"[{audit_id}] Full prompt: {full_prompt[:200]}...") + + # Step 2: Run PoI - Get responses from multiple nodes + logger.info(f"[{audit_id}] Running PoI validation...") + poi_result = self.poi_engine.submit_and_validate( + prompt=full_prompt, + num_nodes=settings.poi_num_nodes, + similarity_threshold=settings.poi_similarity_threshold + ) + + if not poi_result.get('passed') or not poi_result.get('consensus_output'): + logger.error(f"[{audit_id}] PoI validation failed") + return self._create_failed_result( # REMOVED await + audit_id=audit_id, + agent_id=agent_id, + agent_name=agent_name, + task_description=task_description, + task_input=task_input, + category=category, + error="PoI validation failed - inconsistent or no responses", + poi_result=poi_result + ) + + # Get the agent's output (canonical response from PoI) + agent_output = poi_result.get('consensus_output', '') + logger.info(f"[{audit_id}] Agent output: {agent_output[:200]}...") + + # Step 3: Run PoUW - Validate the quality of the output + logger.info(f"[{audit_id}] Running PoUW validation...") + pouw_result = self.pouw_engine.validate( + task_description=task_description, + task_input=task_input, + agent_output=agent_output, + num_validators=settings.pouw_num_validators + ) + + # Step 4: Calculate final confidence score + poi_similarity = poi_result.get('similarity_score', 0.0) + pouw_score = pouw_result.quality_score + + confidence_score = self.evidence_generator.calculate_final_confidence( + poi_similarity=poi_similarity, + pouw_score=pouw_score + ) + + # Step 5: Generate evidence bundle + logger.info(f"[{audit_id}] Generating evidence...") + + # Convert PoUW result to dict for evidence + pouw_result_dict = { + 'is_valid': pouw_result.is_valid, + 'overall_score': pouw_result.quality_score, + 'overall_score_raw': pouw_result.quality_score * 10, + 'passed': pouw_result.is_valid, + 'num_validators': len(pouw_result.validator_scores), + 'criterion_scores': pouw_result.validation_details.get('criteria_scores', {}) + } + + evidence = self.evidence_generator.generate_bundle( + audit_id=audit_id, + agent_id=agent_id, + task_description=task_description, + task_input=task_input, + poi_result=poi_result, + pouw_result=pouw_result_dict, + final_confidence=confidence_score + ) + + # Step 6: Save audit to database + self._save_audit( # REMOVED await + audit_id=audit_id, + agent_id=agent_id, + agent_name=agent_name, + task_description=task_description, + task_input=task_input, + category=category, + poi_similarity=poi_similarity, + pouw_score=pouw_score, + confidence_score=confidence_score, + status="completed", + ipfs_cid=evidence.get('ipfs_cid'), + evidence_hash=evidence.get('bundle_hash') + ) + + elapsed_time = time.time() - start_time + logger.info(f"[{audit_id}] Audit completed in {elapsed_time:.2f}s") + + return { + "audit_id": audit_id, + "agent_id": agent_id, + "status": "completed", + "confidence_score": confidence_score, + "poi_similarity": poi_similarity, + "pouw_mean_score": pouw_score, + "ipfs_cid": evidence.get('ipfs_cid'), + "timestamp": datetime.utcnow().isoformat(), + "elapsed_time": elapsed_time + } + + except Exception as e: + logger.error(f"[{audit_id}] Audit failed: {str(e)}", exc_info=True) + return self._create_failed_result( # REMOVED await + audit_id=audit_id, + agent_id=agent_id, + agent_name=agent_name, + task_description=task_description, + task_input=task_input, + category=category, + error=str(e) + ) + + def _build_agent_prompt(self, task_description: str, task_input: str) -> str: + """Build the full prompt to send to the agent""" + # Enhanced prompt with clearer instructions for code generation + system_context = """You are an AI assistant that follows instructions precisely. +If asked to write code, respond with ONLY the code - no explanations, no markdown formatting, no comments unless specifically requested. +If given an input value, use it as a test case reference but still provide the complete solution as requested.""" + + if task_input and task_input.strip(): + return f"""{system_context} + +Task: {task_description} + +Example Input: {task_input} + +Output:""" + else: + return f"""{system_context} + +Task: {task_description} + +Output:""" + + def _ensure_agent_exists(self, agent_id: str, agent_name: Optional[str]): # REMOVED async + """Ensure agent exists in database""" + with get_db_context() as db: + agent = db.query(Agent).filter(Agent.agent_id == agent_id).first() + if not agent: + agent = Agent( + agent_id=agent_id, + name=agent_name or agent_id + ) + db.add(agent) + # Commit is handled by context manager + + def _save_audit( # REMOVED async + self, + audit_id: str, + agent_id: str, + agent_name: Optional[str], + task_description: str, + task_input: str, + category: str, + poi_similarity: float, + pouw_score: float, + confidence_score: float, + status: str, + ipfs_cid: Optional[str] = None, + evidence_hash: Optional[str] = None + ): + """Save audit to database""" + with get_db_context() as db: + audit = Audit( + audit_id=audit_id, + agent_id=agent_id, + agent_name=agent_name, + task_description=task_description, + task_input=task_input, + category=category, + poi_similarity=poi_similarity, + pouw_mean_score=pouw_score, + final_confidence=confidence_score, + status=status, + ipfs_cid=ipfs_cid, + evidence_hash=evidence_hash, + timestamp=datetime.utcnow(), + completed_at=datetime.utcnow() if status == "completed" else None + ) + db.add(audit) + # Commit handled by context manager + + def _create_failed_result( # REMOVED async + self, + audit_id: str, + agent_id: str, + agent_name: Optional[str], + task_description: str, + task_input: str, + category: str, + error: str, + poi_result: Dict = None + ) -> Dict[str, Any]: + """Create a failed audit result""" + # Save failed audit to database + with get_db_context() as db: + audit = Audit( + audit_id=audit_id, + agent_id=agent_id, + agent_name=agent_name, + task_description=task_description, + task_input=task_input, + category=category, + status="failed", + final_confidence=0.0, + poi_similarity=poi_result.get('similarity_score', 0.0) if poi_result else 0.0, + pouw_mean_score=0.0, + timestamp=datetime.utcnow() + ) + db.add(audit) + # Commit handled by context manager + + return { + "audit_id": audit_id, + "agent_id": agent_id, + "status": "failed", + "error": error, + "confidence_score": 0.0, + "poi_similarity": poi_result.get('similarity_score', 0.0) if poi_result else 0.0, + "pouw_mean_score": 0.0, + "timestamp": datetime.utcnow().isoformat() + } + + async def get_audit_status(self, audit_id: str) -> Dict[str, Any]: + """Get audit status and results""" + with get_db_context() as db: + audit = db.query(Audit).filter(Audit.audit_id == audit_id).first() + if not audit: + raise ValueError(f"Audit {audit_id} not found") + + return { + "audit_id": audit.audit_id, + "agent_id": audit.agent_id, + "status": audit.status, + "confidence_score": audit.final_confidence, + "poi_similarity": audit.poi_similarity, + "pouw_score": audit.pouw_mean_score, + "timestamp": audit.timestamp.isoformat() if audit.timestamp else None + } + + +# Global orchestrator instance +orchestrator = AuditOrchestrator() diff --git a/apps/AgentAuditor/backend/requirements.txt b/apps/AgentAuditor/backend/requirements.txt new file mode 100644 index 0000000..e72fefd --- /dev/null +++ b/apps/AgentAuditor/backend/requirements.txt @@ -0,0 +1,12 @@ +fastapi +uvicorn[standard] +sqlalchemy +psycopg2-binary +pydantic-settings +python-dotenv +web3 +eth-account +sentence-transformers +scikit-learn +numpy +requests \ No newline at end of file diff --git a/apps/AgentAuditor/backend/web3_client.py b/apps/AgentAuditor/backend/web3_client.py new file mode 100644 index 0000000..c72a282 --- /dev/null +++ b/apps/AgentAuditor/backend/web3_client.py @@ -0,0 +1,256 @@ +""" +Web3 client for interacting with Cortensor contracts on Arbitrum Sepolia +""" +import json +import time +import logging +from pathlib import Path +from web3 import Web3 +from eth_account import Account +from typing import Dict, Any, List, Tuple + +from backend.config import settings + +logger = logging.getLogger(__name__) + + +class CortensorWeb3Client: + """Client for interacting with Cortensor SessionV2 and SessionQueueV2 contracts""" + + def __init__(self): + # Connect to Arbitrum Sepolia + self.w3 = Web3(Web3.HTTPProvider(settings.arbitrum_sepolia_rpc_url)) + + # Add PoA middleware for Arbitrum compatibility (web3.py v7+ compatible) + try: + # Try web3.py v7+ method + from web3.middleware import ExtraDataToPOAMiddleware + self.w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0) + except ImportError: + try: + # Fallback for web3.py v6.x + from web3.middleware import geth_poa_middleware + self.w3.middleware_onion.inject(geth_poa_middleware, layer=0) + except ImportError: + # No middleware needed for some versions + logger.warning("Could not load PoA middleware, continuing without it") + + # Load account from private key (if provided) + if settings.private_key: + self.account = Account.from_key(settings.private_key) + self.w3.eth.default_account = self.account.address + else: + self.account = None + logger.warning("No private key provided - read-only mode") + + # Load contract ABIs + abi_dir = Path(__file__).parent.parent + + try: + with open(abi_dir / "SessionV2ABI.json", "r") as f: + session_v2_abi = json.load(f) + with open(abi_dir / "SessionQueueV2ABI.json", "r") as f: + session_queue_v2_abi = json.load(f) + + # Initialize contract instances + if settings.session_v2_address: + self.session_v2 = self.w3.eth.contract( + address=Web3.to_checksum_address(settings.session_v2_address), + abi=session_v2_abi + ) + else: + self.session_v2 = None + + if settings.session_queue_v2_address: + self.session_queue_v2 = self.w3.eth.contract( + address=Web3.to_checksum_address(settings.session_queue_v2_address), + abi=session_queue_v2_abi + ) + else: + self.session_queue_v2 = None + + except FileNotFoundError as e: + logger.warning(f"ABI files not found: {e}") + self.session_v2 = None + self.session_queue_v2 = None + + logger.info(f"Web3 client initialized. Connected: {self.w3.is_connected()}") + if self.account: + logger.info(f"Account address: {self.account.address}") + + def create_session( + self, + session_name: str, + model: str, + redundant: int = 3, + num_validators: int = 0, + task_timeout: int = 180 + ) -> Tuple[int, str]: + """Create a new Cortensor session""" + + # Check for pre-configured session ID + if settings.cortensor_session_id > 0: + logger.info(f"Using existing Session ID: {settings.cortensor_session_id}") + return settings.cortensor_session_id, "0x0" + + if not self.session_v2 or not self.account: + raise ValueError("Web3 client not properly configured for transactions") + + try: + tx = self.session_v2.functions.create( + session_name, + model, + self.account.address, + redundant, + redundant, + redundant, + num_validators, + 0, + False + ).build_transaction({ + 'from': self.account.address, + 'nonce': self.w3.eth.get_transaction_count(self.account.address), + 'gas': 500000, + 'gasPrice': self.w3.eth.gas_price + }) + + signed_tx = self.w3.eth.account.sign_transaction(tx, self.account.key) + tx_hash = self.w3.eth.send_raw_transaction(signed_tx.raw_transaction) + receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash) + + session_created_event = self.session_v2.events.SessionCreated().process_receipt(receipt) + session_id = session_created_event[0]['args']['sessionId'] + + logger.info(f"Session created: ID={session_id}, TX={tx_hash.hex()}") + return session_id, tx_hash.hex() + + except Exception as e: + logger.error(f"Failed to create session: {e}") + raise + + def submit_task(self, session_id: int, prompt: str, model_params: Dict[str, Any] = None) -> Tuple[int, str]: + """Submit a task to an existing session""" + + if not self.session_v2 or not self.account: + raise ValueError("Web3 client not properly configured for transactions") + + try: + if model_params is None: + llm_params = [2048, 700, 950, 50, 0, 0] + else: + llm_params = [ + model_params.get('max_tokens', 2048), + int(model_params.get('temperature', 0.7) * 1000), + int(model_params.get('top_p', 0.95) * 1000), + model_params.get('top_k', 50), + int(model_params.get('presence_penalty', 0) * 1000), + int(model_params.get('frequency_penalty', 0) * 1000) + ] + + tx = self.session_v2.functions.submit( + session_id, + 0, + prompt, + 0, + "", + llm_params, + "" + ).build_transaction({ + 'from': self.account.address, + 'nonce': self.w3.eth.get_transaction_count(self.account.address), + 'gas': 300000, + 'gasPrice': self.w3.eth.gas_price + }) + + signed_tx = self.w3.eth.account.sign_transaction(tx, self.account.key) + tx_hash = self.w3.eth.send_raw_transaction(signed_tx.raw_transaction) + receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash) + + task_submitted_event = self.session_v2.events.TaskSubmitted().process_receipt(receipt) + task_id = task_submitted_event[0]['args']['taskId'] + + logger.info(f"Task submitted: Session={session_id}, Task={task_id}") + return task_id, tx_hash.hex() + + except Exception as e: + logger.error(f"Failed to submit task: {e}") + raise + + def get_task_results(self, session_id: int, task_id: int) -> List[Dict[str, Any]]: + """Retrieve all results for a task""" + + if not self.session_queue_v2: + raise ValueError("SessionQueueV2 contract not configured") + + try: + miners, outputs = self.session_queue_v2.functions.getTaskResults( + session_id, + task_id + ).call() + + results = [] + for miner, output in zip(miners, outputs): + results.append({ + 'miner': miner, + 'output': output + }) + + return results + + except Exception as e: + logger.error(f"Failed to get task results: {e}") + raise + + def get_session_info(self, session_id: int) -> Dict[str, Any]: + """Get detailed session information""" + + if not self.session_v2: + return {"session_id": session_id, "status": "unknown"} + + try: + session = self.session_v2.functions.getSession(session_id).call() + return { + 'id': session[0], + 'name': session[2], + 'state': session[4], + 'redundant': session[11] + } + except Exception as e: + logger.error(f"Failed to get session info: {e}") + return {"session_id": session_id, "error": str(e)} + + def deactivate_session(self, session_id: int) -> str: + """Deactivate a session after use""" + + # Don't deactivate the shared session + if settings.cortensor_session_id > 0 and session_id == settings.cortensor_session_id: + logger.info(f"Skipping deactivation of shared session {session_id}") + return "0x0" + + if not self.session_v2 or not self.account: + return "0x0" + + try: + tx = self.session_v2.functions.deactivateSession( + session_id + ).build_transaction({ + 'from': self.account.address, + 'nonce': self.w3.eth.get_transaction_count(self.account.address), + 'gas': 200000, + 'gasPrice': self.w3.eth.gas_price + }) + + signed_tx = self.w3.eth.account.sign_transaction(tx, self.account.key) + tx_hash = self.w3.eth.send_raw_transaction(signed_tx.raw_transaction) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + + logger.info(f"Session {session_id} deactivated") + return tx_hash.hex() + + except Exception as e: + logger.warning(f"Failed to deactivate session: {e}") + return "0x0" + + +# Global client instance +web3_client = CortensorWeb3Client() diff --git a/apps/AgentAuditor/database/schema.sql b/apps/AgentAuditor/database/schema.sql new file mode 100644 index 0000000..95b6aa5 --- /dev/null +++ b/apps/AgentAuditor/database/schema.sql @@ -0,0 +1,181 @@ +-- Cortensor Agent Auditor Database Schema +-- PostgreSQL 14+ + +-- Enable UUID extension +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- Agents table: Registry of all agents with reputation tracking +CREATE TABLE IF NOT EXISTS agents ( + agent_id VARCHAR(255) PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + category VARCHAR(100), -- 'code', 'content', 'reasoning', 'medical', 'legal', etc. + overall_confidence FLOAT DEFAULT 0.0 CHECK (overall_confidence >= 0 AND overall_confidence <= 1), + total_audits INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_audit_at TIMESTAMP, + + -- Indexes + CONSTRAINT agents_confidence_check CHECK (overall_confidence >= 0 AND overall_confidence <= 1) +); + +CREATE INDEX idx_agents_confidence ON agents(overall_confidence DESC); +CREATE INDEX idx_agents_category ON agents(category); +CREATE INDEX idx_agents_last_audit ON agents(last_audit_at DESC); + +-- Audits table: Individual audit records +CREATE TABLE IF NOT EXISTS audits ( + audit_id VARCHAR(255) PRIMARY KEY, + agent_id VARCHAR(255) NOT NULL REFERENCES agents(agent_id) ON DELETE CASCADE, + + -- Task details + task_description TEXT NOT NULL, + task_input TEXT NOT NULL, + category VARCHAR(100), + + -- Cortensor session details + session_id_poi INTEGER, + session_id_pouw INTEGER, + task_id INTEGER, + + -- Results + confidence_score FLOAT CHECK (confidence_score >= 0 AND confidence_score <= 1), + poi_similarity FLOAT CHECK (poi_similarity >= 0 AND poi_similarity <= 1), + pouw_mean_score FLOAT CHECK (pouw_mean_score >= 0 AND pouw_mean_score <= 1), + consensus_output TEXT, + + -- Evidence + evidence_bundle_ipfs_hash VARCHAR(255), + evidence_bundle_json JSONB, + + -- Status tracking + status VARCHAR(50) DEFAULT 'pending', -- pending, processing, completed, failed + + -- Timestamps + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + completed_at TIMESTAMP, + + -- Constraints + CONSTRAINT audits_confidence_check CHECK (confidence_score >= 0 AND confidence_score <= 1) +); + +CREATE INDEX idx_audits_agent_id ON audits(agent_id); +CREATE INDEX idx_audits_status ON audits(status); +CREATE INDEX idx_audits_created_at ON audits(created_at DESC); +CREATE INDEX idx_audits_confidence ON audits(confidence_score DESC); +CREATE INDEX idx_audits_ipfs_hash ON audits(evidence_bundle_ipfs_hash); + +-- Reputation history: Historical snapshots for trend analysis +CREATE TABLE IF NOT EXISTS reputation_history ( + id SERIAL PRIMARY KEY, + agent_id VARCHAR(255) NOT NULL REFERENCES agents(agent_id) ON DELETE CASCADE, + + confidence_score FLOAT NOT NULL CHECK (confidence_score >= 0 AND confidence_score <= 1), + total_audits INTEGER NOT NULL, + trend VARCHAR(20), -- 'improving', 'declining', 'stable' + + recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_reputation_history_agent_id ON reputation_history(agent_id); +CREATE INDEX idx_reputation_history_recorded_at ON reputation_history(recorded_at DESC); + +-- Validator stats: Track validator performance and consistency +CREATE TABLE IF NOT EXISTS validator_stats ( + validator_address VARCHAR(255) PRIMARY KEY, + total_validations INTEGER DEFAULT 0, + average_score_given FLOAT DEFAULT 0.0, + consistency_score FLOAT DEFAULT 0.0, -- How often they agree with consensus + + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_validator_stats_consistency ON validator_stats(consistency_score DESC); + +-- Function to update agent reputation (called after each audit) +CREATE OR REPLACE FUNCTION update_agent_reputation() +RETURNS TRIGGER AS $$ +BEGIN + -- Update agent's overall confidence using exponential moving average + UPDATE agents + SET + overall_confidence = CASE + WHEN overall_confidence = 0 THEN NEW.confidence_score + ELSE (0.3 * NEW.confidence_score) + (0.7 * overall_confidence) + END, + total_audits = total_audits + 1, + last_audit_at = NEW.completed_at + WHERE agent_id = NEW.agent_id; + + -- Insert reputation history snapshot + INSERT INTO reputation_history (agent_id, confidence_score, total_audits, trend) + SELECT + NEW.agent_id, + overall_confidence, + total_audits, + CASE + WHEN overall_confidence > LAG(overall_confidence) OVER (ORDER BY last_audit_at) THEN 'improving' + WHEN overall_confidence < LAG(overall_confidence) OVER (ORDER BY last_audit_at) THEN 'declining' + ELSE 'stable' + END + FROM agents + WHERE agent_id = NEW.agent_id; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Trigger to auto-update reputation after audit completion +CREATE TRIGGER trigger_update_agent_reputation +AFTER INSERT OR UPDATE OF confidence_score ON audits +FOR EACH ROW +WHEN (NEW.status = 'completed' AND NEW.confidence_score IS NOT NULL) +EXECUTE FUNCTION update_agent_reputation(); + +-- View: Agent leaderboard +CREATE OR REPLACE VIEW agent_leaderboard AS +SELECT + a.agent_id, + a.name, + a.category, + a.overall_confidence, + a.total_audits, + a.last_audit_at, + COUNT(DISTINCT au.audit_id) FILTER (WHERE au.created_at > NOW() - INTERVAL '30 days') as audits_last_30_days, + AVG(au.confidence_score) FILTER (WHERE au.created_at > NOW() - INTERVAL '30 days') as avg_confidence_30d +FROM agents a +LEFT JOIN audits au ON a.agent_id = au.agent_id AND au.status = 'completed' +GROUP BY a.agent_id, a.name, a.category, a.overall_confidence, a.total_audits, a.last_audit_at +ORDER BY a.overall_confidence DESC; + +-- View: Recent audits with agent info +CREATE OR REPLACE VIEW recent_audits_with_agents AS +SELECT + au.audit_id, + au.agent_id, + a.name as agent_name, + a.category, + au.task_description, + au.confidence_score, + au.poi_similarity, + au.pouw_mean_score, + au.status, + au.evidence_bundle_ipfs_hash, + au.created_at, + au.completed_at +FROM audits au +JOIN agents a ON au.agent_id = a.agent_id +ORDER BY au.created_at DESC; + +-- Seed some example data (optional) +INSERT INTO agents (agent_id, name, description, category) VALUES +('agent-code-assistant', 'Code Assistant', 'Helps with code generation and debugging', 'code'), +('agent-content-writer', 'Content Writer', 'Generates marketing and blog content', 'content'), +('agent-data-analyst', 'Data Analyst', 'Analyzes data and generates insights', 'reasoning') +ON CONFLICT (agent_id) DO NOTHING; + +COMMENT ON TABLE agents IS 'Registry of AI agents with reputation tracking'; +COMMENT ON TABLE audits IS 'Individual audit records with PoI and PoUW results'; +COMMENT ON TABLE reputation_history IS 'Historical reputation snapshots for trend analysis'; +COMMENT ON TABLE validator_stats IS 'Performance metrics for validator nodes'; diff --git a/apps/AgentAuditor/frontend/index.html b/apps/AgentAuditor/frontend/index.html new file mode 100644 index 0000000..4b4d2dd --- /dev/null +++ b/apps/AgentAuditor/frontend/index.html @@ -0,0 +1,13 @@ + + +
+ + + +{value}
+{label}
+ {subValue && ( +{subValue}
+ )} +No reputation history yet
++ {audit.task_description} +
++ The agent you're looking for doesn't exist or hasn't been audited yet. +
+ +
+ {agentId}
+
+
+ No audits found for this agent
++ {audit.task_description} +
+
+ {audit.audit_id}
+
+
+
+ {audit.agent_id}
+
+ + Browse and search all completed agent audits +
++ {searchTerm ? 'Try adjusting your search terms' : 'No audits have been submitted yet'} +
+{label}
+{value}
++ {agent.agent_id?.slice(0, 16)}... +
++ Monitor and verify AI agent performance across the Cortensor network +
++ Please check your connection and try again +
+ ++ Submit your first audit to get started +
+ ++ Validate an AI agent's output using Proof of Inference and Proof of Useful Work +
++ Verification successful +
+
+ {result.audit_id}
+
+
+