diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..06dbd449 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +# Base image +FROM node:18-alpine + +# Set working directory +WORKDIR /app + +# Copy files +COPY package*.json ./ +RUN npm install + +COPY . . + +# Expose port +EXPOSE 3000 + +# Start app +CMD ["npm", "start"] diff --git a/ReadMe.md b/ReadMe.md index 4a9dfa39..f3adc0cf 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1 +1,56 @@ -See [PiRC1: Pi Ecosystem Token Design](./PiRC1/ReadMe.md) \ No newline at end of file +See [PiRC1: Pi Ecosystem Token Design](./PiRC1/ReadMe.md) + + +🚀 PiRC AI Oracle – Health Monitoring Engine + +Overview + +PiRC AI Oracle is an intelligent monitoring system for Pi Testnet RPC nodes. +It analyzes network health and automatically determines risk levels and system actions. + +Features + +- 🔍 Real-time RPC health monitoring ("getHealth") +- 🧠 AI-based risk analysis engine +- ⚡ Automated decision system (failover, throttling) +- 🌐 REST API integration + +Architecture + +Pi RPC → AI Oracle → Risk Engine → Action Engine → API + +API Endpoint + +GET /api/health-check + +Example Response + +{ + "health": { + "status": "degraded", + "txCount": 1200, + "ledger": 9500 + }, + "analysis": { + "risk": "HIGH", + "score": 80, + "insight": "⚠️ Network anomaly detected" + }, + "action": { + "action": "SWITCH_RPC", + "message": "Switching to backup node..." + } +} + +Installation + +npm install +npm start + +Vision + +To build a self-adaptive AI-powered monitoring layer for decentralized infrastructure. + +--- + +🔥 Built for Pi Network ecosystem developers diff --git a/engine/actionEngine.js b/engine/actionEngine.js new file mode 100644 index 00000000..966f9418 --- /dev/null +++ b/engine/actionEngine.js @@ -0,0 +1,40 @@ +import { sendTelegramAlert } from "../alerts/telegramAlert.js"; +import { switchRPC } from "../services/rpcManager.js"; +import { triggerProtection } from "../blockchain/contractTrigger.js"; + +export async function takeAction(analysis) { + // HIGH RISK + if (analysis.risk === "HIGH") { + // kirim alert + await sendTelegramAlert( + ` *Pi Network Alert*\nRisk: HIGH\nScore: ${analysis.score}\n${analysis.insight}` + ); + + // switch RPC + const newRpc = switchRPC(); + + // trigger protection (optional) + await triggerProtection("PAUSE"); + + return { + action: "EMERGENCY_MODE", + rpc: newRpc, + protection: "ENABLED", + message: "High risk detected: RPC switched & protection activated" + }; + } + + // MEDIUM RISK + if (analysis.risk === "MEDIUM") { + return { + action: "THROTTLE", + message: "Reducing request rate..." + }; + } + + // SAFE + return { + action: "NORMAL", + message: "All systems stable" + }; +} diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 00000000..b27bce18 --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,19 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pirc-ai-oracle +spec: + replicas: 2 + selector: + matchLabels: + app: pirc-ai-oracle + template: + metadata: + labels: + app: pirc-ai-oracle + spec: + containers: + - name: pirc-ai-oracle + image: pirc-ai-oracle:latest + ports: + - containerPort: 3000 diff --git a/k8s/service.yaml b/k8s/service.yaml new file mode 100644 index 00000000..cf92120b --- /dev/null +++ b/k8s/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: pirc-service +spec: + type: NodePort + selector: + app: pirc-ai-oracle + ports: + - port: 80 + targetPort: 3000 + nodePort: 30007 diff --git a/oracle/aiOracle.js b/oracle/aiOracle.js new file mode 100644 index 00000000..b0edb069 --- /dev/null +++ b/oracle/aiOracle.js @@ -0,0 +1,28 @@ +export function analyzeHealth(health) { + let riskScore = 0; + let status = "SAFE"; + + if (health.status !== "healthy") riskScore += 40; + if (health.txCount > 1000) riskScore += 30; + if (health.ledger < 10000) riskScore += 20; + + if (riskScore > 70) status = "HIGH"; + else if (riskScore > 40) status = "MEDIUM"; + + return { + risk: status, + score: riskScore, + insight: generateInsight(status) + }; +} + +function generateInsight(status) { + switch (status) { + case "HIGH": + return "⚠️ Network anomaly detected"; + case "MEDIUM": + return "⚡ Moderate load detected"; + default: + return "✅ Network stable"; + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..debdb201 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "pirc-ai-oracle", + "version": "1.0.0", + "description": "AI-powered health monitoring for Pi Testnet RPC", + "main": "src/app.js", + "type": "module", + "scripts": { + "start": "node src/app.js" + }, + "dependencies": { + "express": "^4.18.2", + "node-fetch": "^3.3.2" + } +} diff --git a/routes/healthRoute.js b/routes/healthRoute.js new file mode 100644 index 00000000..ef3194cf --- /dev/null +++ b/routes/healthRoute.js @@ -0,0 +1,22 @@ +import express from "express"; +import { getPiHealth } from "../services/piHealth.js"; +import { analyzeHealth } from "../oracle/aiOracle.js"; +import { takeAction } from "../engine/actionEngine.js"; + +const router = express.Router(); + +router.get("/health-check", async (req, res) => { + try { + const health = await getPiHealth(); + const analysis = analyzeHealth(health); + const action = takeAction(analysis); + + res.json({ health, analysis, action }); + } catch (err) { + res.status(500).json({ + error: "Failed to fetch health" + }); + } +}); + +export default router; diff --git a/services/piHealth.js b/services/piHealth.js new file mode 100644 index 00000000..4221a352 --- /dev/null +++ b/services/piHealth.js @@ -0,0 +1,20 @@ +import fetch from "node-fetch"; +import { getCurrentRPC } from "./rpcManager.js"; + +export async function getPiHealth() { + const rpc = getCurrentRPC(); + + const res = await fetch(rpc, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "getHealth" + }) + }); + + return res.json(); +} diff --git a/src/alerts/telegramAlert.js b/src/alerts/telegramAlert.js new file mode 100644 index 00000000..f87dd13c --- /dev/null +++ b/src/alerts/telegramAlert.js @@ -0,0 +1,22 @@ +import fetch from "node-fetch"; + +const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN; +const CHAT_ID = process.env.TELEGRAM_CHAT_ID; + +export async function sendTelegramAlert(message) { + if (!BOT_TOKEN || !CHAT_ID) return; + + const url = `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`; + + await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + chat_id: CHAT_ID, + text: message, + parse_mode: "Markdown" + }) + }); +} diff --git a/src/app.js b/src/app.js new file mode 100644 index 00000000..2b082c6f --- /dev/null +++ b/src/app.js @@ -0,0 +1,38 @@ +import express from "express"; +import { getPiHealth } from "./services/piHealth.js"; +import { analyzeHealth } from "./oracle/aiOracle.js"; +import { takeAction } from "./engine/actionEngine.js"; + +// import metrics +import metricsRoute, { updateMetrics } from "./routes/metrics.js"; + +const app = express(); + +app.use(express.json()); + +// register endpoint metrics +app.use("/metrics", metricsRoute); + +// endpoint utama +app.get("/health-check", async (req, res) => { + try { + const health = await getPiHealth(); + const analysis = analyzeHealth(health); + const action = await takeAction(analysis); + + // update metrics DI SINI (bukan di luar) + updateMetrics({ health, analysis, action }); + + res.json({ + health, + analysis, + action + }); + } catch (err) { + res.status(500).json({ + error: "Failed to fetch health" + }); + } +}); + +export default app; diff --git a/src/blockchain/contractTrigger.js b/src/blockchain/contractTrigger.js new file mode 100644 index 00000000..26f56c46 --- /dev/null +++ b/src/blockchain/contractTrigger.js @@ -0,0 +1,10 @@ +import { triggerProtection } from "../blockchain/contractTrigger.js"; + +if (analysis.risk === "HIGH") { + await triggerProtection("PAUSE"); + + return { + action: "PAUSE_NETWORK", + message: "Smart contract paused" + }; +} diff --git a/src/blockchain/oracleBridge.js b/src/blockchain/oracleBridge.js new file mode 100644 index 00000000..0415f958 --- /dev/null +++ b/src/blockchain/oracleBridge.js @@ -0,0 +1,9 @@ +export async function sendToBlockchain(data) { + console.log("📡 Sending AI result to blockchain..."); + + // pseudo-call + return { + txHash: "0xORACLE123", + data + }; +} diff --git a/src/blockchain/piContract.js b/src/blockchain/piContract.js new file mode 100644 index 00000000..d0d47092 --- /dev/null +++ b/src/blockchain/piContract.js @@ -0,0 +1,8 @@ +export async function pauseNetwork() { + console.log("⛔ Pausing network via smart contract..."); + + return { + txHash: "0xPAUSE", + status: "success" + }; +} diff --git a/src/config/rpcList.js b/src/config/rpcList.js new file mode 100644 index 00000000..3b555d61 --- /dev/null +++ b/src/config/rpcList.js @@ -0,0 +1,5 @@ +export const RPC_ENDPOINTS = [ + "https://rpc.testnet.minepi.com", + "https://rpc-backup1.minepi.com", + "https://rpc-backup2.minepi.com" +]; diff --git a/src/ml/anomalyModel.js b/src/ml/anomalyModel.js new file mode 100644 index 00000000..5d8f709b --- /dev/null +++ b/src/ml/anomalyModel.js @@ -0,0 +1,23 @@ +import * as tf from "@tensorflow/tfjs"; + +let model; + +export async function loadModel() { + model = tf.sequential(); + model.add(tf.layers.dense({ units: 8, inputShape: [2], activation: "relu" })); + model.add(tf.layers.dense({ units: 1, activation: "sigmoid" })); + + model.compile({ + optimizer: "adam", + loss: "binaryCrossentropy" + }); +} + +export function predictAnomaly(txCount, ledger) { + if (!model) return 0; + + const input = tf.tensor2d([[txCount, ledger]]); + const output = model.predict(input); + + return output.dataSync()[0]; +} diff --git a/src/routes/metrics.js b/src/routes/metrics.js new file mode 100644 index 00000000..37961944 --- /dev/null +++ b/src/routes/metrics.js @@ -0,0 +1,15 @@ +import express from "express"; + +const router = express.Router(); + +let latestData = {}; + +export function updateMetrics(data) { + latestData = data; +} + +router.get("/metrics", (req, res) => { + res.json(latestData); +}); + +export default router; diff --git a/src/server.js b/src/server.js new file mode 100644 index 00000000..be4333c8 --- /dev/null +++ b/src/server.js @@ -0,0 +1,7 @@ +import app from "./app.js"; + +const PORT = process.env.PORT || 3000; + +app.listen(PORT, () => { + console.log(`🚀 PiRC AI Oracle running on port ${PORT}`); +}); diff --git a/src/services/rpcManager.js b/src/services/rpcManager.js new file mode 100644 index 00000000..8af76327 --- /dev/null +++ b/src/services/rpcManager.js @@ -0,0 +1,12 @@ +import { RPC_ENDPOINTS } from "../config/rpcList.js"; + +let currentIndex = 0; + +export function getCurrentRPC() { + return RPC_ENDPOINTS[currentIndex]; +} + +export function switchRPC() { + currentIndex = (currentIndex + 1) % RPC_ENDPOINTS.length; + return getCurrentRPC(); +}