From 6cf72f7632b8ea298cf6fe823159bb3d18b8e19d Mon Sep 17 00:00:00 2001 From: ethan Date: Tue, 12 Nov 2024 15:07:09 -0500 Subject: [PATCH 01/51] added --- .gitignore | 51 +++- docker-compose.yml | 48 +++ execution-role-policy.json | 12 + orchestrator/Dockerfile | 23 ++ orchestrator/README.md | 6 + orchestrator/package.json | 26 ++ orchestrator/src/app.ts | 286 ++++++++++++++++++ orchestrator/tsconfig.json | 16 + task-definition.json | 76 +++++ task-role-policy.json | 12 + verifiable-delay-function/Dockerfile | 35 +++ verifiable-delay-function/main.py | 18 +- .../verifiable_delay_function_converter.py | 1 + 13 files changed, 600 insertions(+), 10 deletions(-) create mode 100644 docker-compose.yml create mode 100644 execution-role-policy.json create mode 100644 orchestrator/Dockerfile create mode 100644 orchestrator/package.json create mode 100644 orchestrator/src/app.ts create mode 100644 orchestrator/tsconfig.json create mode 100644 task-definition.json create mode 100644 task-role-policy.json create mode 100644 verifiable-delay-function/Dockerfile diff --git a/.gitignore b/.gitignore index eba74f4..77d110f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,50 @@ -venv/ \ No newline at end of file +# Ignore virtual environments +venv/ +.env/ +.env +env + +# Ignore distribution/build directories +dist/ +build/ +*.egg-info/ +*.pyc +__pycache__/ + +# Ignore Node.js dependencies +node_modules/ +**/node_modules/ + +# Ignore Terraform and related files +terraform/ +**/.terraform/ +*.tfstate +*.tfstate.backup + +# Ignore package locks and dependency files +package-lock.json +yarn.lock + +# Ignore OS-generated files +.DS_Store +Thumbs.db + +# Ignore editor-specific files +.idea/ +.vscode/ + +# Ignore logs and temporary files +logs/ +*.log +temp/ +.tmp/ +*.swp + +# Ignore Python bytecode and cache files +__pycache__/ +*.py[cod] + +# Ignore Git-specific large files +*.pack +*.idx + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1341f34 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +version: '3.8' + +services: + postgres: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER:-myuser} + POSTGRES_PASSWORD: ${DB_PASSWORD:-mypassword} + POSTGRES_DB: ${DB_NAME:-mydatabase} + ports: + - "5432:5432" + networks: + - backend + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-myuser} -d ${DB_NAME:-mydatabase}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator: + image: randao/orchestrator:v0.1.3 + depends_on: + postgres: + condition: service_healthy + environment: + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: ${DB_USER:-myuser} + DB_PASSWORD: ${DB_PASSWORD:-mypassword} + DB_NAME: ${DB_NAME:-mydatabase} + ENVIRONMENT: local + DOCKER_NETWORK: backend # Passing the network name + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock # Mount Docker socket + +networks: + backend: + name: backend # This will set the network name explicitly + driver: bridge + + +volumes: + pgdata: + driver: local diff --git a/execution-role-policy.json b/execution-role-policy.json new file mode 100644 index 0000000..b833d12 --- /dev/null +++ b/execution-role-policy.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} diff --git a/orchestrator/Dockerfile b/orchestrator/Dockerfile new file mode 100644 index 0000000..5c17fc9 --- /dev/null +++ b/orchestrator/Dockerfile @@ -0,0 +1,23 @@ +# Use the official Node.js image as the base image +FROM node:16 + +# Create and set the working directory +WORKDIR /usr/src/app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the entire project into the container +COPY . . + +# Compile TypeScript to JavaScript +RUN npx tsc + +# Expose the port if needed +EXPOSE 3000 + +# Run the compiled app +CMD ["node", "dist/app.js"] diff --git a/orchestrator/README.md b/orchestrator/README.md index e69de29..14ca0d2 100644 --- a/orchestrator/README.md +++ b/orchestrator/README.md @@ -0,0 +1,6 @@ +cd into repo + +docker login +docker build -t satoshispalace/orchestrator:latest -t satoshispalace/orchestrator:v0.0.30 . +docker push satoshispalace/orchestrator:v0.0.30 +docker push satoshispalace/orchestrator:latest diff --git a/orchestrator/package.json b/orchestrator/package.json new file mode 100644 index 0000000..19385c4 --- /dev/null +++ b/orchestrator/package.json @@ -0,0 +1,26 @@ +{ + "devDependencies": { + "@types/dockerode": "^3.3.31", + "@types/node": "^22.9.0", + "@types/pg": "^8.11.10", + "serverless-offline": "^14.3.3", + "typescript": "^5.6.3" + }, + "dependencies": { + "aws-sdk": "^2.1692.0", + "axios": "^1.7.7", + "crypto": "^1.0.1", + "dockerode": "^4.0.2", + "pg": "^8.13.1" + }, + "name": "ao-vrf", + "description": "1. To build:\r ```\r docker build -t serverless-multi-cloud .\r ```", + "version": "1.0.0", + "main": "Organizer.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts new file mode 100644 index 0000000..0e53d17 --- /dev/null +++ b/orchestrator/src/app.ts @@ -0,0 +1,286 @@ +import { Client } from 'pg'; +import Docker from 'dockerode'; +import AWS from 'aws-sdk'; +const docker = new Docker(); + +// Database configuration +const dbConfig = { + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT || '5432', 10), + user: process.env.DB_USER || 'myuser', + password: process.env.DB_PASSWORD || 'mypassword', + database: process.env.DB_NAME || 'mydatabase', +}; + +// Constants for configuration +const POLLING_INTERVAL_MS = 100; +const MINIMUM_ENTRIES = 50; +const TARGET_ENTRIES = 75; +const DROP_CHANCE = 0.005; +//Expected increments per second=10×0.005=0.05 +//180 times per hour +//4,320 times per day +//1,576,800 times per year +const MAX_OUTSTANDING_REQUESTS = 10; +const MAX_RETRIES = 10; +const RETRY_DELAY_MS = 10000; +const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.0'; +const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; +const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); +const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks +const ongoingContainers = new Set(); // Track container IDs of running Docker containers + +let ongoingRequest = false; + +// Retry logic for connecting to PostgreSQL +async function connectWithRetry(): Promise { + const client = new Client(dbConfig); + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + await client.connect(); + console.log(`Connected to PostgreSQL database (Attempt ${attempt})`); + return client; + } catch (error) { + console.error(`Connection attempt ${attempt} failed, retrying in ${RETRY_DELAY_MS / 1000} seconds...`); + await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS)); + } + } + throw new Error("Failed to connect to PostgreSQL after multiple attempts"); +} + +// Setup the `verifiable_delay_functions` table if not exists +async function setupDatabase(client: Client): Promise { + await client.query(` + CREATE TABLE IF NOT EXISTS verifiable_delay_functions ( + id TEXT PRIMARY KEY, -- Define as TEXT to match UUID format + request_id TEXT, + modulus TEXT NOT NULL, + input TEXT NOT NULL, + output TEXT NOT NULL, + proof JSON NOT NULL, + date TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + console.log("Database setup complete. 'verifiable_delay_functions' table is ready."); +} + +// Added variable to track the number of Spot instance interruptions +let spotInterruptions = 0; + +// Modified function to trigger VDF job pod using ECS or Docker +async function triggerVDFJobPod(): Promise { + if (ENVIRONMENT === 'cloud') { + try { + console.log("Cloud environment detected. Launching ECS task."); + const result = await ecs.runTask({ + cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', + taskDefinition: 'vdf-job', + capacityProviderStrategy: [ + { + capacityProvider: 'FARGATE_SPOT', + weight: 1 + } + ], + networkConfiguration: { + awsvpcConfiguration: { + subnets: [process.env.SUBNET_ID || 'subnet-12345678'], + securityGroups: [process.env.SECURITY_GROUP || 'sg-12345678'], + assignPublicIp: 'ENABLED' + } + }, + overrides: { + containerOverrides: [ + { + name: 'vdf_job_container', + environment: [ + { name: 'DATABASE_TYPE', value: 'postgresql' }, + { name: 'DATABASE_HOST', value: process.env.DB_HOST || 'cloud-postgres-host' }, + { name: 'DATABASE_PORT', value: process.env.DB_PORT || '5432' }, + { name: 'DATABASE_USER', value: process.env.DB_USER || 'myuser' }, + { name: 'DATABASE_PASSWORD', value: process.env.DB_PASSWORD || 'mypassword' }, + { name: 'DATABASE_NAME', value: process.env.DB_NAME || 'mydatabase' }, + ] + } + ] + }, + count: 1 + }).promise(); + + const taskArn = result.tasks?.[0]?.taskArn; + if (taskArn) { + ongoingTasks.add(taskArn); + console.log(`ECS task started successfully: ${taskArn}`); + return taskArn; + } + return null; + } catch (error: any) { + if (error?.code === 'SpotCapacityNotAvailableException') { + spotInterruptions++; + console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); + } else { + console.error("Error launching ECS task:", error); + } + return null; + } + } else { + const containerName = `vdf_job_${Date.now()}`; + console.log(`Starting Docker container with name: ${containerName}`); + const container = await docker.createContainer({ + Image: VDF_JOB_IMAGE, + Cmd: ['python', 'main.py'], + Env: [ + `DATABASE_TYPE=postgresql`, + `DATABASE_HOST=${dbConfig.host}`, + `DATABASE_PORT=${dbConfig.port.toString()}`, + `DATABASE_USER=${dbConfig.user}`, + `DATABASE_PASSWORD=${dbConfig.password}`, + `DATABASE_NAME=${dbConfig.database}`, + ], + HostConfig: { + NetworkMode: 'backend', + }, + name: containerName + }); + + await container.start(); + ongoingContainers.add(container.id); + console.log(`Docker container ${containerName} started successfully.`); + return container.id; + } +} + +// Modified function to wait for ECS tasks to complete and remove them from tracking +async function monitorECSTasks(): Promise { + if (ongoingTasks.size === 0) return; + + const describeTasksResult = await ecs.describeTasks({ + cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', + tasks: Array.from(ongoingTasks) + }).promise(); + + describeTasksResult.tasks?.forEach(task => { + // Check capacity provider name to determine if it is running Fargate or Fargate Spot + const capacityProvider = task.capacityProviderName || 'Unknown'; + + console.log(`ECS task: ${task.taskArn}, Capacity Provider: ${capacityProvider}`); + + if (task.lastStatus === 'STOPPED') { + console.log(`ECS task stopped: ${task.taskArn}`); + if (task.stoppedReason) { + console.log(`Task stopped reason: ${task.stoppedReason}`); + if (task.stoppedReason.includes('Host EC2 instance termination')) { + spotInterruptions++; + console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); + } + } else { + console.log('Task stopped due to normal completion.'); + } + ongoingTasks.delete(task.taskArn as string); + } + }); +} + + + + +// Function to wait for Docker containers to complete and remove them from tracking +async function monitorDockerContainers(): Promise { + if (ongoingContainers.size === 0) return; + + for (const containerId of ongoingContainers) { + try { + const container = docker.getContainer(containerId); + const containerInfo = await container.inspect(); + + if (containerInfo.State.Status === 'exited') { + console.log(`Docker container stopped: ${containerId}`); + await container.remove(); + ongoingContainers.delete(containerId); + } + } catch (error) { + console.error(`Error monitoring Docker container ${containerId}:`, error); + ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) + } + } +} + +// Function to check if there are fewer than MINIMUM_ENTRIES and fetch until TARGET_ENTRIES +async function checkAndFetchIfNeeded(client: Client): Promise { + try { + if (ongoingRequest) return; + + const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); + const currentCount = parseInt(res.rows[0].count, 10); + + if (currentCount < MINIMUM_ENTRIES) { + const entriesNeeded = TARGET_ENTRIES - currentCount; + console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries to reach ${TARGET_ENTRIES}...`); + ongoingRequest = true; + let tasksTriggered = 0; + + while (tasksTriggered < entriesNeeded) { + if (ENVIRONMENT === 'cloud' && ongoingTasks.size >= MAX_OUTSTANDING_REQUESTS) { + await monitorECSTasks(); + } else if (ENVIRONMENT !== 'cloud' && ongoingContainers.size >= MAX_OUTSTANDING_REQUESTS) { + await monitorDockerContainers(); + } else { + const taskArn = await triggerVDFJobPod(); + if (taskArn) { + tasksTriggered++; + console.log(`Task triggered: ${taskArn}. Total ongoing tasks: ${ongoingTasks.size}. Total ongoing containers ${+ ongoingContainers.size}`); + } + } + } + ongoingRequest = false; + } + } catch (error) { + console.error('Error during check and fetch:', error); + ongoingRequest = false; + } +} + +// Polling function to manage entries and delete old ones occasionally +async function polling(client: Client): Promise { + if (Math.random() < DROP_CHANCE) { + try { + console.log("Randomly chosen to log and delete the oldest entry..."); + const res = await client.query('SELECT * FROM verifiable_delay_functions ORDER BY id ASC LIMIT 1'); + const entry = res.rows[0]; + + if (entry) { + console.log("Logging and deleting oldest entry:", JSON.stringify(entry, null, 2)); + await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [entry.id]); + console.log("Oldest entry deleted from database."); + } else { + console.log("No entries available to delete."); + } + } catch (error) { + console.error('Error logging and deleting oldest entry:', error); + } + } else { + await checkAndFetchIfNeeded(client); + } +} + +// Main function +async function run(): Promise { + const client = await connectWithRetry(); + await setupDatabase(client); + + setInterval(async () => { + await polling(client); + }, POLLING_INTERVAL_MS); + + setInterval(async () => { + const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); + console.log(`Periodic log - Current database size: ${res.rows[0].count}`); + }, 10000); + + process.on("SIGTERM", async () => { + console.log("SIGTERM received. Closing database connection."); + await client.end(); + process.exit(0); + }); +} + +run().catch((err) => console.error(`Error in main function: ${err}`)); diff --git a/orchestrator/tsconfig.json b/orchestrator/tsconfig.json new file mode 100644 index 0000000..493e143 --- /dev/null +++ b/orchestrator/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "module": "commonjs", + "target": "es6", + "strict": true, + "esModuleInterop": true + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/task-definition.json b/task-definition.json new file mode 100644 index 0000000..cf7e617 --- /dev/null +++ b/task-definition.json @@ -0,0 +1,76 @@ +{ + "family": "orchestrator-service", + "networkMode": "awsvpc", + "containerDefinitions": [ + { + "name": "postgres", + "image": "postgres:13", + "essential": true, + "environment": [ + { "name": "POSTGRES_USER", "value": "myuser" }, + { "name": "POSTGRES_PASSWORD", "value": "mypassword" }, + { "name": "POSTGRES_DB", "value": "mydatabase" } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "ecs-orchestrator-service", + "awslogs-region": "us-east-1", + "awslogs-stream-prefix": "postgres" + } + }, + "healthCheck": { + "command": [ + "CMD-SHELL", + "pg_isready -U myuser -d mydatabase" + ], + "interval": 10, + "timeout": 5, + "retries": 5 + }, + "mountPoints": [ + { + "sourceVolume": "pgdata", + "containerPath": "/var/lib/postgresql/data" + } + ] + }, + { + "name": "orchestrator", + "image": "satoshispalace/orchestrator:latest", + "essential": true, + "environment": [ + { "name": "DB_HOST", "value": "postgres" }, + { "name": "DB_PORT", "value": "5432" }, + { "name": "DB_USER", "value": "myuser" }, + { "name": "DB_PASSWORD", "value": "mypassword" }, + { "name": "DB_NAME", "value": "mydatabase" } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "ecs-orchestrator-service", + "awslogs-region": "us-east-1", + "awslogs-stream-prefix": "orchestrator" + } + }, + "mountPoints": [] + } + ], + "volumes": [ + { + "name": "pgdata", + "efsVolumeConfiguration": { + "fileSystemId": "fs-01a952c26605adac6", + "rootDirectory": "/" + } + } + ], + "requiresCompatibilities": [ + "FARGATE" + ], + "cpu": "512", + "memory": "1024", + "executionRoleArn": "arn:aws:iam::615299754404:role/orchestrator-service-execution-role", + "taskRoleArn": "arn:aws:iam::615299754404:role/orchestrator-service-task-role" +} diff --git a/task-role-policy.json b/task-role-policy.json new file mode 100644 index 0000000..b833d12 --- /dev/null +++ b/task-role-policy.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} diff --git a/verifiable-delay-function/Dockerfile b/verifiable-delay-function/Dockerfile new file mode 100644 index 0000000..1064f5f --- /dev/null +++ b/verifiable-delay-function/Dockerfile @@ -0,0 +1,35 @@ +# Use an official Python image as a base +FROM python:3.8-slim + +# Install system dependencies needed for gmpy2 and PostgreSQL connection +RUN apt-get update && apt-get install -y \ + libgmp-dev \ + libmpfr-dev \ + libmpc-dev \ + gcc \ + && rm -rf /var/lib/apt/lists/* + +# Set up a working directory +WORKDIR /app + +# Copy only the requirements file to leverage Docker cache +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the application code +COPY . . + +# Set environment variables for PostgreSQL credentials (can be overridden at runtime) +ENV DB_NAME=mydatabase \ + DB_USER=myuser \ + DB_PASSWORD=mypassword \ + DB_HOST=localhost \ + DB_PORT=5432 + +# Expose any necessary ports (optional, specify if your app uses specific ports) +# EXPOSE 8000 + +# Command to run the main script +# CMD ["python", "main.py"] diff --git a/verifiable-delay-function/main.py b/verifiable-delay-function/main.py index 6d061be..f7a4762 100644 --- a/verifiable-delay-function/main.py +++ b/verifiable-delay-function/main.py @@ -21,16 +21,16 @@ def main(): print("VDF output (y):", y) print(f"Proof generation time: {generation_time:.4f} seconds") - # Time the parallel verification - start_time = time.time() - is_valid_parallel = vdf.parallel_verify(y, proof) - parallel_verification_time = time.time() - start_time - print("Parallel verification:", "Valid" if is_valid_parallel else "Invalid") - print(f"Parallel verification time: {parallel_verification_time:.4f} seconds") + # # Time the parallel verification + # start_time = time.time() + # is_valid_parallel = vdf.parallel_verify(y, proof) + # parallel_verification_time = time.time() - start_time + # print("Parallel verification:", "Valid" if is_valid_parallel else "Invalid") + # print(f"Parallel verification time: {parallel_verification_time:.4f} seconds") - if not is_valid_parallel: - print("Verification failed. Aborting save.") - return + # if not is_valid_parallel: + # print("Verification failed. Aborting save.") + # return # Convert the puzzle instance to a VerifiableDelayFunctionEntity for database storage entity: VerifiableDelayFunctionEntity = conver_verifiable_delay_function_to_entity(vdf) diff --git a/verifiable-delay-function/src/converters/verifiable_delay_function_converter.py b/verifiable-delay-function/src/converters/verifiable_delay_function_converter.py index 3a378fa..399eba6 100644 --- a/verifiable-delay-function/src/converters/verifiable_delay_function_converter.py +++ b/verifiable-delay-function/src/converters/verifiable_delay_function_converter.py @@ -1,4 +1,5 @@ from typing import List +from gmpy2 import mpz from src.database.entity.verifiable_delay_function_entity import VerifiableDelayFunctionEntity from src.verifiable_delay_function.verifiable_delay_function import VerifiableDelayFunction From cbfff849c4261dc96f31f5ac144c720caa3011b6 Mon Sep 17 00:00:00 2001 From: ethan Date: Mon, 25 Nov 2024 12:29:52 -0500 Subject: [PATCH 02/51] added new requester --- .gitignore | 8 +- docker-compose.yml | 19 +- orchestrator/.dockerignore | 3 + orchestrator/Dockerfile | 2 +- orchestrator/README.md | 4 +- orchestrator/package.json | 5 +- orchestrator/src/app.ts | 52 +++- orchestrator/src/test.ts | 51 ++++ requester/.dockerignore | 3 + requester/Dockerfile | 23 ++ requester/docs/development.md | 0 requester/orchestrator/.dockerignore | 3 + requester/orchestrator/Dockerfile | 23 ++ requester/orchestrator/README.md | 6 + requester/orchestrator/docs/development.md | 0 requester/orchestrator/package.json | 27 ++ requester/orchestrator/src/app.ts | 313 +++++++++++++++++++++ requester/orchestrator/src/test.ts | 51 ++++ requester/orchestrator/tsconfig.json | 16 ++ requester/package.json | 27 ++ requester/src/app.ts | 96 +++++++ requester/tsconfig.json | 16 ++ terraform/backend.tf | 0 terraform/debug_task.tf | 36 +++ terraform/ecs_cluster.tf | 16 ++ terraform/ecs_service.tf | 35 +++ terraform/iam_roles.tf | 80 ++++++ terraform/logging.tf | 6 + terraform/networking.tf | 30 ++ terraform/outputs.tf | 5 + terraform/package.json | 8 + terraform/posgress.tf | 24 ++ terraform/providers.tf | 12 + terraform/task_definitions.tf | 73 +++++ terraform/variables.tf | 25 ++ 35 files changed, 1082 insertions(+), 16 deletions(-) create mode 100644 orchestrator/.dockerignore create mode 100644 orchestrator/src/test.ts create mode 100644 requester/.dockerignore create mode 100644 requester/Dockerfile create mode 100644 requester/docs/development.md create mode 100644 requester/orchestrator/.dockerignore create mode 100644 requester/orchestrator/Dockerfile create mode 100644 requester/orchestrator/README.md create mode 100644 requester/orchestrator/docs/development.md create mode 100644 requester/orchestrator/package.json create mode 100644 requester/orchestrator/src/app.ts create mode 100644 requester/orchestrator/src/test.ts create mode 100644 requester/orchestrator/tsconfig.json create mode 100644 requester/package.json create mode 100644 requester/src/app.ts create mode 100644 requester/tsconfig.json create mode 100644 terraform/backend.tf create mode 100644 terraform/debug_task.tf create mode 100644 terraform/ecs_cluster.tf create mode 100644 terraform/ecs_service.tf create mode 100644 terraform/iam_roles.tf create mode 100644 terraform/logging.tf create mode 100644 terraform/networking.tf create mode 100644 terraform/outputs.tf create mode 100644 terraform/package.json create mode 100644 terraform/posgress.tf create mode 100644 terraform/providers.tf create mode 100644 terraform/task_definitions.tf create mode 100644 terraform/variables.tf diff --git a/.gitignore b/.gitignore index 77d110f..16af4e4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ venv/ .env/ .env env +wallet.json # Ignore distribution/build directories dist/ @@ -10,16 +11,19 @@ build/ *.egg-info/ *.pyc __pycache__/ +pgdata/ # Ignore Node.js dependencies node_modules/ **/node_modules/ # Ignore Terraform and related files -terraform/ -**/.terraform/ *.tfstate *.tfstate.backup +*.exe +*.lock.* +LICENSE.txt + # Ignore package locks and dependency files package-lock.json diff --git a/docker-compose.yml b/docker-compose.yml index 1341f34..9a289c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.1.3 + image: randao/orchestrator:v0.1.8 depends_on: postgres: condition: service_healthy @@ -31,18 +31,33 @@ services: DB_PASSWORD: ${DB_PASSWORD:-mypassword} DB_NAME: ${DB_NAME:-mydatabase} ENVIRONMENT: local + PATH_TO_WALLET: /app/wallet.json # Path inside the container + WALLET_JSON: ${WALLET_JSON} + PROVIDER_ID: ${PROVIDER_ID} DOCKER_NETWORK: backend # Passing the network name networks: - backend volumes: - /var/run/docker.sock:/var/run/docker.sock # Mount Docker socket + - ./wallet.json:/app/wallet.json # Mount wallet.json into the container + + requester: + image: randao/requester:v0.1.1 + environment: + PATH_TO_WALLET: /app/wallet.json # Path inside the container + REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} + PROVIDER_ID: ${PROVIDER_ID} + DOCKER_NETWORK: backend # Passing the network name + networks: + - backend + volumes: + - ./wallet.json:/app/wallet.json # Mount wallet.json into the container networks: backend: name: backend # This will set the network name explicitly driver: bridge - volumes: pgdata: driver: local diff --git a/orchestrator/.dockerignore b/orchestrator/.dockerignore new file mode 100644 index 0000000..a0bedda --- /dev/null +++ b/orchestrator/.dockerignore @@ -0,0 +1,3 @@ +node_modules +.git +dist diff --git a/orchestrator/Dockerfile b/orchestrator/Dockerfile index 5c17fc9..e0b8740 100644 --- a/orchestrator/Dockerfile +++ b/orchestrator/Dockerfile @@ -1,5 +1,5 @@ # Use the official Node.js image as the base image -FROM node:16 +FROM node:20 # Create and set the working directory WORKDIR /usr/src/app diff --git a/orchestrator/README.md b/orchestrator/README.md index 14ca0d2..5470a55 100644 --- a/orchestrator/README.md +++ b/orchestrator/README.md @@ -1,6 +1,6 @@ cd into repo docker login -docker build -t satoshispalace/orchestrator:latest -t satoshispalace/orchestrator:v0.0.30 . -docker push satoshispalace/orchestrator:v0.0.30 +docker build -t randao/orchestrator:latest -t randao/orchestrator:v0.1.4 . +docker push satoshispalace/orchestrator:v0.1.4 docker push satoshispalace/orchestrator:latest diff --git a/orchestrator/package.json b/orchestrator/package.json index 19385c4..01f8b49 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -1,12 +1,13 @@ { "devDependencies": { "@types/dockerode": "^3.3.31", - "@types/node": "^22.9.0", + "@types/node": "^22.9.1", "@types/pg": "^8.11.10", "serverless-offline": "^14.3.3", "typescript": "^5.6.3" }, "dependencies": { + "ao-process-clients": "^2.3.2", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", @@ -23,4 +24,4 @@ "keywords": [], "author": "", "license": "ISC" -} +} \ No newline at end of file diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 0e53d17..b0d65c0 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -1,6 +1,17 @@ import { Client } from 'pg'; import Docker from 'dockerode'; import AWS from 'aws-sdk'; +import { getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomClientConfig } from "ao-process-clients" + +// const RANDOM_CONFIG: RandomClientConfig = { +// tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, +// processId: getRandomClientAutoConfiguration().processId, +// wallet: JSON.parse(process.env.WALLET_JSON!), +// environment: 'mainnet' +// } +const randclient: IRandomClient = RandomClient.autoConfiguration() +//const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG) + const docker = new Docker(); // Database configuration @@ -13,7 +24,7 @@ const dbConfig = { }; // Constants for configuration -const POLLING_INTERVAL_MS = 100; +const POLLING_INTERVAL_MS = 500; const MINIMUM_ENTRIES = 50; const TARGET_ENTRIES = 75; const DROP_CHANCE = 0.005; @@ -29,6 +40,9 @@ const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks const ongoingContainers = new Set(); // Track container IDs of running Docker containers +const PROVIDER_ID = process.env.PROVIDER_ID || "0"; + + let ongoingRequest = false; @@ -211,7 +225,9 @@ async function checkAndFetchIfNeeded(client: Client): Promise { const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); const currentCount = parseInt(res.rows[0].count, 10); - + const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(currentCount); + console.log("Updates onchain: " + updateAvailableValuesResult) + console.log(await randclient.getProviderAvailableValues(PROVIDER_ID)) if (currentCount < MINIMUM_ENTRIES) { const entriesNeeded = TARGET_ENTRIES - currentCount; console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries to reach ${TARGET_ENTRIES}...`); @@ -241,6 +257,27 @@ async function checkAndFetchIfNeeded(client: Client): Promise { // Polling function to manage entries and delete old ones occasionally async function polling(client: Client): Promise { + await checkAndFetchIfNeeded(client); + + try { + console.log(PROVIDER_ID); + + const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); + console.log(openRequests); + + if (openRequests && openRequests.activeRequests && openRequests.activeRequests.request_ids) { + console.log(openRequests.providerId); + console.log(openRequests.activeRequests); + console.log(openRequests.activeRequests.request_ids); + console.log(openRequests.activeRequests.request_ids.length); + } else { + console.log('No requests'); + } + } catch (error) { + console.error('An error occurred while fetching open random requests:', error); + } + + if (Math.random() < DROP_CHANCE) { try { console.log("Randomly chosen to log and delete the oldest entry..."); @@ -258,7 +295,6 @@ async function polling(client: Client): Promise { console.error('Error logging and deleting oldest entry:', error); } } else { - await checkAndFetchIfNeeded(client); } } @@ -267,15 +303,17 @@ async function run(): Promise { const client = await connectWithRetry(); await setupDatabase(client); - setInterval(async () => { - await polling(client); - }, POLLING_INTERVAL_MS); - setInterval(async () => { const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); console.log(`Periodic log - Current database size: ${res.rows[0].count}`); }, 10000); + setInterval(async () => { + await polling(client); + }, POLLING_INTERVAL_MS); + + + process.on("SIGTERM", async () => { console.log("SIGTERM received. Closing database connection."); await client.end(); diff --git a/orchestrator/src/test.ts b/orchestrator/src/test.ts new file mode 100644 index 0000000..2b56b81 --- /dev/null +++ b/orchestrator/src/test.ts @@ -0,0 +1,51 @@ +import { IRandomClient, RandomClient } from "ao-process-clients"; + +const PROVIDER_ID = process.env.PROVIDER_ID || "0"; + +async function main() { + try { + const randclient: IRandomClient = RandomClient.autoConfiguration(); + + console.log("Testing `createRequest`..."); + const createRequestResult = await randclient.createRequest(["provider1", "provider2"]); + console.log("createRequest result:", createRequestResult); + + console.log("Testing `getOpenRandomRequests`..."); + const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); + console.log("getOpenRandomRequests result:", openRequests); + + console.log("Testing `getProviderAvailableValues`..."); + const availableValues = await randclient.getProviderAvailableValues(PROVIDER_ID); + console.log("getProviderAvailableValues result:", availableValues); + + console.log("Testing `getRandomRequests`..."); + const randomRequests = await randclient.getRandomRequests(["request1", "request2"]); + console.log("getRandomRequests result:", randomRequests); + + console.log("Testing `postVDFChallenge`..."); + const postVDFChallengeResult = await randclient.postVDFChallenge( + "request1", + "modulus_value", + "input_value" + ); + console.log("postVDFChallenge result:", postVDFChallengeResult); + + console.log("Testing `postVDFOutputAndProof`..."); + const postVDFOutputAndProofResult = await randclient.postVDFOutputAndProof( + "request1", + "output_value", + "proof_value" + ); + console.log("postVDFOutputAndProof result:", postVDFOutputAndProofResult); + + console.log("Testing `updateProviderAvailableValues`..."); + const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(42); + console.log("updateProviderAvailableValues result:", updateAvailableValuesResult); + + } catch (error) { + console.error("An error occurred during testing:", error); + } +} + +// Call the main function +main(); diff --git a/requester/.dockerignore b/requester/.dockerignore new file mode 100644 index 0000000..a0bedda --- /dev/null +++ b/requester/.dockerignore @@ -0,0 +1,3 @@ +node_modules +.git +dist diff --git a/requester/Dockerfile b/requester/Dockerfile new file mode 100644 index 0000000..e0b8740 --- /dev/null +++ b/requester/Dockerfile @@ -0,0 +1,23 @@ +# Use the official Node.js image as the base image +FROM node:20 + +# Create and set the working directory +WORKDIR /usr/src/app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the entire project into the container +COPY . . + +# Compile TypeScript to JavaScript +RUN npx tsc + +# Expose the port if needed +EXPOSE 3000 + +# Run the compiled app +CMD ["node", "dist/app.js"] diff --git a/requester/docs/development.md b/requester/docs/development.md new file mode 100644 index 0000000..e69de29 diff --git a/requester/orchestrator/.dockerignore b/requester/orchestrator/.dockerignore new file mode 100644 index 0000000..a0bedda --- /dev/null +++ b/requester/orchestrator/.dockerignore @@ -0,0 +1,3 @@ +node_modules +.git +dist diff --git a/requester/orchestrator/Dockerfile b/requester/orchestrator/Dockerfile new file mode 100644 index 0000000..e0b8740 --- /dev/null +++ b/requester/orchestrator/Dockerfile @@ -0,0 +1,23 @@ +# Use the official Node.js image as the base image +FROM node:20 + +# Create and set the working directory +WORKDIR /usr/src/app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the entire project into the container +COPY . . + +# Compile TypeScript to JavaScript +RUN npx tsc + +# Expose the port if needed +EXPOSE 3000 + +# Run the compiled app +CMD ["node", "dist/app.js"] diff --git a/requester/orchestrator/README.md b/requester/orchestrator/README.md new file mode 100644 index 0000000..5470a55 --- /dev/null +++ b/requester/orchestrator/README.md @@ -0,0 +1,6 @@ +cd into repo + +docker login +docker build -t randao/orchestrator:latest -t randao/orchestrator:v0.1.4 . +docker push satoshispalace/orchestrator:v0.1.4 +docker push satoshispalace/orchestrator:latest diff --git a/requester/orchestrator/docs/development.md b/requester/orchestrator/docs/development.md new file mode 100644 index 0000000..e69de29 diff --git a/requester/orchestrator/package.json b/requester/orchestrator/package.json new file mode 100644 index 0000000..01f8b49 --- /dev/null +++ b/requester/orchestrator/package.json @@ -0,0 +1,27 @@ +{ + "devDependencies": { + "@types/dockerode": "^3.3.31", + "@types/node": "^22.9.1", + "@types/pg": "^8.11.10", + "serverless-offline": "^14.3.3", + "typescript": "^5.6.3" + }, + "dependencies": { + "ao-process-clients": "^2.3.2", + "aws-sdk": "^2.1692.0", + "axios": "^1.7.7", + "crypto": "^1.0.1", + "dockerode": "^4.0.2", + "pg": "^8.13.1" + }, + "name": "ao-vrf", + "description": "1. To build:\r ```\r docker build -t serverless-multi-cloud .\r ```", + "version": "1.0.0", + "main": "Organizer.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/requester/orchestrator/src/app.ts b/requester/orchestrator/src/app.ts new file mode 100644 index 0000000..33c4cab --- /dev/null +++ b/requester/orchestrator/src/app.ts @@ -0,0 +1,313 @@ +import { Client } from 'pg'; +import Docker from 'dockerode'; +import AWS from 'aws-sdk'; +import { getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomClientConfig } from "ao-process-clients" + +// const RANDOM_CONFIG: RandomClientConfig = { +// tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, +// processId: getRandomClientAutoConfiguration().processId, +// wallet: JSON.parse(process.env.WALLET_JSON!), +// environment: 'mainnet' +// } +const randclient: IRandomClient = RandomClient.autoConfiguration() +//const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG) + +const docker = new Docker(); + +// Database configuration +const dbConfig = { + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT || '5432', 10), + user: process.env.DB_USER || 'myuser', + password: process.env.DB_PASSWORD || 'mypassword', + database: process.env.DB_NAME || 'mydatabase', +}; + +// Constants for configuration +const POLLING_INTERVAL_MS = 100; +const MINIMUM_ENTRIES = 50; +const TARGET_ENTRIES = 75; +const DROP_CHANCE = 0.005; +//Expected increments per second=10×0.005=0.05 +//180 times per hour +//4,320 times per day +//1,576,800 times per year +const MAX_OUTSTANDING_REQUESTS = 10; +const MAX_RETRIES = 10; +const RETRY_DELAY_MS = 10000; +const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.0'; +const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; +const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); +const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks +const ongoingContainers = new Set(); // Track container IDs of running Docker containers +const PROVIDER_ID = process.env.PROVIDER_ID || "0"; + + + +let ongoingRequest = false; + +// Retry logic for connecting to PostgreSQL +async function connectWithRetry(): Promise { + const client = new Client(dbConfig); + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + await client.connect(); + console.log(`Connected to PostgreSQL database (Attempt ${attempt})`); + return client; + } catch (error) { + console.error(`Connection attempt ${attempt} failed, retrying in ${RETRY_DELAY_MS / 1000} seconds...`); + await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS)); + } + } + throw new Error("Failed to connect to PostgreSQL after multiple attempts"); +} + +// Setup the `verifiable_delay_functions` table if not exists +async function setupDatabase(client: Client): Promise { + await client.query(` + CREATE TABLE IF NOT EXISTS verifiable_delay_functions ( + id TEXT PRIMARY KEY, -- Define as TEXT to match UUID format + request_id TEXT, + modulus TEXT NOT NULL, + input TEXT NOT NULL, + output TEXT NOT NULL, + proof JSON NOT NULL, + date TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + console.log("Database setup complete. 'verifiable_delay_functions' table is ready."); +} + +// Added variable to track the number of Spot instance interruptions +let spotInterruptions = 0; + +// Modified function to trigger VDF job pod using ECS or Docker +async function triggerVDFJobPod(): Promise { + if (ENVIRONMENT === 'cloud') { + try { + console.log("Cloud environment detected. Launching ECS task."); + const result = await ecs.runTask({ + cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', + taskDefinition: 'vdf-job', + capacityProviderStrategy: [ + { + capacityProvider: 'FARGATE_SPOT', + weight: 1 + } + ], + networkConfiguration: { + awsvpcConfiguration: { + subnets: [process.env.SUBNET_ID || 'subnet-12345678'], + securityGroups: [process.env.SECURITY_GROUP || 'sg-12345678'], + assignPublicIp: 'ENABLED' + } + }, + overrides: { + containerOverrides: [ + { + name: 'vdf_job_container', + environment: [ + { name: 'DATABASE_TYPE', value: 'postgresql' }, + { name: 'DATABASE_HOST', value: process.env.DB_HOST || 'cloud-postgres-host' }, + { name: 'DATABASE_PORT', value: process.env.DB_PORT || '5432' }, + { name: 'DATABASE_USER', value: process.env.DB_USER || 'myuser' }, + { name: 'DATABASE_PASSWORD', value: process.env.DB_PASSWORD || 'mypassword' }, + { name: 'DATABASE_NAME', value: process.env.DB_NAME || 'mydatabase' }, + ] + } + ] + }, + count: 1 + }).promise(); + + const taskArn = result.tasks?.[0]?.taskArn; + if (taskArn) { + ongoingTasks.add(taskArn); + console.log(`ECS task started successfully: ${taskArn}`); + return taskArn; + } + return null; + } catch (error: any) { + if (error?.code === 'SpotCapacityNotAvailableException') { + spotInterruptions++; + console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); + } else { + console.error("Error launching ECS task:", error); + } + return null; + } + } else { + const containerName = `vdf_job_${Date.now()}`; + console.log(`Starting Docker container with name: ${containerName}`); + const container = await docker.createContainer({ + Image: VDF_JOB_IMAGE, + Cmd: ['python', 'main.py'], + Env: [ + `DATABASE_TYPE=postgresql`, + `DATABASE_HOST=${dbConfig.host}`, + `DATABASE_PORT=${dbConfig.port.toString()}`, + `DATABASE_USER=${dbConfig.user}`, + `DATABASE_PASSWORD=${dbConfig.password}`, + `DATABASE_NAME=${dbConfig.database}`, + ], + HostConfig: { + NetworkMode: 'backend', + }, + name: containerName + }); + + await container.start(); + ongoingContainers.add(container.id); + console.log(`Docker container ${containerName} started successfully.`); + return container.id; + } +} + +// Modified function to wait for ECS tasks to complete and remove them from tracking +async function monitorECSTasks(): Promise { + if (ongoingTasks.size === 0) return; + + const describeTasksResult = await ecs.describeTasks({ + cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', + tasks: Array.from(ongoingTasks) + }).promise(); + + describeTasksResult.tasks?.forEach(task => { + // Check capacity provider name to determine if it is running Fargate or Fargate Spot + const capacityProvider = task.capacityProviderName || 'Unknown'; + + console.log(`ECS task: ${task.taskArn}, Capacity Provider: ${capacityProvider}`); + + if (task.lastStatus === 'STOPPED') { + console.log(`ECS task stopped: ${task.taskArn}`); + if (task.stoppedReason) { + console.log(`Task stopped reason: ${task.stoppedReason}`); + if (task.stoppedReason.includes('Host EC2 instance termination')) { + spotInterruptions++; + console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); + } + } else { + console.log('Task stopped due to normal completion.'); + } + ongoingTasks.delete(task.taskArn as string); + } + }); +} + + + + +// Function to wait for Docker containers to complete and remove them from tracking +async function monitorDockerContainers(): Promise { + if (ongoingContainers.size === 0) return; + + for (const containerId of ongoingContainers) { + try { + const container = docker.getContainer(containerId); + const containerInfo = await container.inspect(); + + if (containerInfo.State.Status === 'exited') { + console.log(`Docker container stopped: ${containerId}`); + await container.remove(); + ongoingContainers.delete(containerId); + } + } catch (error) { + console.error(`Error monitoring Docker container ${containerId}:`, error); + ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) + } + } +} + +// Function to check if there are fewer than MINIMUM_ENTRIES and fetch until TARGET_ENTRIES +async function checkAndFetchIfNeeded(client: Client): Promise { + try { + if (ongoingRequest) return; + + const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); + const currentCount = parseInt(res.rows[0].count, 10); + const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(currentCount); + console.log("Updates onchain: " + updateAvailableValuesResult) + console.log(await randclient.getProviderAvailableValues(PROVIDER_ID)) + if (currentCount < MINIMUM_ENTRIES) { + const entriesNeeded = TARGET_ENTRIES - currentCount; + console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries to reach ${TARGET_ENTRIES}...`); + ongoingRequest = true; + let tasksTriggered = 0; + + while (tasksTriggered < entriesNeeded) { + if (ENVIRONMENT === 'cloud' && ongoingTasks.size >= MAX_OUTSTANDING_REQUESTS) { + await monitorECSTasks(); + } else if (ENVIRONMENT !== 'cloud' && ongoingContainers.size >= MAX_OUTSTANDING_REQUESTS) { + await monitorDockerContainers(); + } else { + const taskArn = await triggerVDFJobPod(); + if (taskArn) { + tasksTriggered++; + console.log(`Task triggered: ${taskArn}. Total ongoing tasks: ${ongoingTasks.size}. Total ongoing containers ${+ ongoingContainers.size}`); + } + } + } + ongoingRequest = false; + } + } catch (error) { + console.error('Error during check and fetch:', error); + ongoingRequest = false; + } +} + +// Polling function to manage entries and delete old ones occasionally +async function polling(client: Client): Promise { + await checkAndFetchIfNeeded(client); + + console.log(PROVIDER_ID) + var openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID) + console.log(openRequests) + console.log(openRequests.providerId) + console.log(openRequests.activeRequests) + console.log(openRequests.activeRequests.request_ids) + console.log(openRequests.activeRequests.request_ids.length) + + if (Math.random() < DROP_CHANCE) { + try { + console.log("Randomly chosen to log and delete the oldest entry..."); + const res = await client.query('SELECT * FROM verifiable_delay_functions ORDER BY id ASC LIMIT 1'); + const entry = res.rows[0]; + + if (entry) { + console.log("Logging and deleting oldest entry:", JSON.stringify(entry, null, 2)); + await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [entry.id]); + console.log("Oldest entry deleted from database."); + } else { + console.log("No entries available to delete."); + } + } catch (error) { + console.error('Error logging and deleting oldest entry:', error); + } + } else { + } +} + +// Main function +async function run(): Promise { + const client = await connectWithRetry(); + await setupDatabase(client); + + setInterval(async () => { + const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); + console.log(`Periodic log - Current database size: ${res.rows[0].count}`); + }, 10000); + + setInterval(async () => { + await polling(client); + }, POLLING_INTERVAL_MS); + + + + process.on("SIGTERM", async () => { + console.log("SIGTERM received. Closing database connection."); + await client.end(); + process.exit(0); + }); +} + +run().catch((err) => console.error(`Error in main function: ${err}`)); diff --git a/requester/orchestrator/src/test.ts b/requester/orchestrator/src/test.ts new file mode 100644 index 0000000..2b56b81 --- /dev/null +++ b/requester/orchestrator/src/test.ts @@ -0,0 +1,51 @@ +import { IRandomClient, RandomClient } from "ao-process-clients"; + +const PROVIDER_ID = process.env.PROVIDER_ID || "0"; + +async function main() { + try { + const randclient: IRandomClient = RandomClient.autoConfiguration(); + + console.log("Testing `createRequest`..."); + const createRequestResult = await randclient.createRequest(["provider1", "provider2"]); + console.log("createRequest result:", createRequestResult); + + console.log("Testing `getOpenRandomRequests`..."); + const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); + console.log("getOpenRandomRequests result:", openRequests); + + console.log("Testing `getProviderAvailableValues`..."); + const availableValues = await randclient.getProviderAvailableValues(PROVIDER_ID); + console.log("getProviderAvailableValues result:", availableValues); + + console.log("Testing `getRandomRequests`..."); + const randomRequests = await randclient.getRandomRequests(["request1", "request2"]); + console.log("getRandomRequests result:", randomRequests); + + console.log("Testing `postVDFChallenge`..."); + const postVDFChallengeResult = await randclient.postVDFChallenge( + "request1", + "modulus_value", + "input_value" + ); + console.log("postVDFChallenge result:", postVDFChallengeResult); + + console.log("Testing `postVDFOutputAndProof`..."); + const postVDFOutputAndProofResult = await randclient.postVDFOutputAndProof( + "request1", + "output_value", + "proof_value" + ); + console.log("postVDFOutputAndProof result:", postVDFOutputAndProofResult); + + console.log("Testing `updateProviderAvailableValues`..."); + const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(42); + console.log("updateProviderAvailableValues result:", updateAvailableValuesResult); + + } catch (error) { + console.error("An error occurred during testing:", error); + } +} + +// Call the main function +main(); diff --git a/requester/orchestrator/tsconfig.json b/requester/orchestrator/tsconfig.json new file mode 100644 index 0000000..493e143 --- /dev/null +++ b/requester/orchestrator/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "module": "commonjs", + "target": "es6", + "strict": true, + "esModuleInterop": true + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/requester/package.json b/requester/package.json new file mode 100644 index 0000000..01f8b49 --- /dev/null +++ b/requester/package.json @@ -0,0 +1,27 @@ +{ + "devDependencies": { + "@types/dockerode": "^3.3.31", + "@types/node": "^22.9.1", + "@types/pg": "^8.11.10", + "serverless-offline": "^14.3.3", + "typescript": "^5.6.3" + }, + "dependencies": { + "ao-process-clients": "^2.3.2", + "aws-sdk": "^2.1692.0", + "axios": "^1.7.7", + "crypto": "^1.0.1", + "dockerode": "^4.0.2", + "pg": "^8.13.1" + }, + "name": "ao-vrf", + "description": "1. To build:\r ```\r docker build -t serverless-multi-cloud .\r ```", + "version": "1.0.0", + "main": "Organizer.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/requester/src/app.ts b/requester/src/app.ts new file mode 100644 index 0000000..5e609ca --- /dev/null +++ b/requester/src/app.ts @@ -0,0 +1,96 @@ +import { + getRandomClientAutoConfiguration, + IRandomClient, + RandomClient, + RandomClientConfig, +} from "ao-process-clients"; + +const PROVIDER_ID = "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE"; +const RETRY_DELAY_MS = 500; +const CHANCE_TO_CALL_RANDOM = 0.05; + +const RANDOM_CONFIG: RandomClientConfig = { + tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, + processId: getRandomClientAutoConfiguration().processId, + wallet: JSON.parse(process.env.REQUEST_WALLET_JSON!), + environment: "mainnet", +}; + +let totalRandomCalled = 0; +let totalTimeToFulfill = 0; +let fulfilledRequests = 0; +const outstandingRequests: Set = new Set(); + +async function main() { + const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG); + + while (true) { + try { + // Roll for random chance to make a request + if (Math.random() < CHANCE_TO_CALL_RANDOM) { + console.log("Initiating random request..."); + await randclient.createRequest([PROVIDER_ID]); + totalRandomCalled++; + console.log("Random request initiated. Awaiting request ID in open requests..."); + } + + // Check open requests + const openRequestsResponse = await randclient.getOpenRandomRequests(PROVIDER_ID); + const openRequestIds = openRequestsResponse.activeRequests.request_ids || []; + console.log("Open requests:", openRequestIds); + + // Track outstanding requests + for (const requestId of openRequestIds) { + if (!outstandingRequests.has(requestId)) { + console.log(`Tracking new request: ${requestId}`); + outstandingRequests.add(requestId); + } + } + + // Check the status of outstanding requests + if (outstandingRequests.size > 0) { + const randomRequestsResponse = await randclient.getRandomRequests(Array.from(outstandingRequests)); + const requests = randomRequestsResponse.randomRequestResponses || []; // Adjust based on actual response structure + console.log(randomRequestsResponse) + console.log(requests) + + // for (const request of requests) { + // const requestId = request.requestId; // Adjust if property has a different name + // if (request?.status === "fulfilled") { + // const fulfilledTime = Date.now(); + // const timeToFulfill = fulfilledTime - request.createdTime; // Adjust if createdTime exists + // totalTimeToFulfill += timeToFulfill; + // fulfilledRequests++; + // console.log(`Request ${requestId} fulfilled. Time to fulfill: ${timeToFulfill}ms`); + // outstandingRequests.delete(requestId); // Stop tracking fulfilled requests + // } else { + // console.log(`Request ${requestId} is still being processed.`); + // } + // } + } + + // Calculate and log stats + if (fulfilledRequests > 0) { + const avgTimeToFulfill = totalTimeToFulfill / fulfilledRequests; + + console.log(` + Total Random Called: ${totalRandomCalled} + Outstanding Requests: ${outstandingRequests.size} + Average Time to Fulfill: ${avgTimeToFulfill}ms + `); + } + + // Wait before next cycle + await delay(RETRY_DELAY_MS); + } catch (error) { + console.error("An error occurred:", error); + } + } +} + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +// Call the main function +main(); diff --git a/requester/tsconfig.json b/requester/tsconfig.json new file mode 100644 index 0000000..493e143 --- /dev/null +++ b/requester/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "module": "commonjs", + "target": "es6", + "strict": true, + "esModuleInterop": true + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/terraform/backend.tf b/terraform/backend.tf new file mode 100644 index 0000000..e69de29 diff --git a/terraform/debug_task.tf b/terraform/debug_task.tf new file mode 100644 index 0000000..9972781 --- /dev/null +++ b/terraform/debug_task.tf @@ -0,0 +1,36 @@ +# debug_task.tf + +resource "aws_ecs_task_definition" "debug_task" { + family = "debug-task" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = "512" + memory = "1024" + + container_definitions = jsonencode([ + { + name = "debug-container" + image = "amazonlinux" + essential = true + command = ["/bin/sh", "-c", "while true; do sleep 60; done"] + environment = [ + { name = "DB_HOST", value = "postgres" }, + { name = "DB_PORT", value = "5432" }, + { name = "DB_USER", value = "myuser" }, + { name = "DB_PASSWORD", value = "mypassword" }, + { name = "DB_NAME", value = "mydatabase" } + ], + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = "/ecs/debug-task" + awslogs-region = var.aws_region + awslogs-stream-prefix = "debug-container" + } + } + } + ]) + + execution_role_arn = aws_iam_role.execution_role.arn + task_role_arn = aws_iam_role.task_role.arn +} diff --git a/terraform/ecs_cluster.tf b/terraform/ecs_cluster.tf new file mode 100644 index 0000000..012ad04 --- /dev/null +++ b/terraform/ecs_cluster.tf @@ -0,0 +1,16 @@ +# ecs_cluster.tf +resource "aws_ecs_cluster" "fargate_cluster" { + name = "fargate-cluster" +} + +# Configure the Capacity Providers for the ECS Cluster +resource "aws_ecs_cluster_capacity_providers" "fargate_cluster_capacity" { + cluster_name = aws_ecs_cluster.fargate_cluster.name + + capacity_providers = ["FARGATE", "FARGATE_SPOT"] + + default_capacity_provider_strategy { + capacity_provider = "FARGATE_SPOT" + weight = 1 + } +} diff --git a/terraform/ecs_service.tf b/terraform/ecs_service.tf new file mode 100644 index 0000000..fb94e60 --- /dev/null +++ b/terraform/ecs_service.tf @@ -0,0 +1,35 @@ +# ecs_service.tf - Updated to Only Deploy Orchestrator Service + +resource "aws_ecs_service" "orchestrator_service" { + name = "orchestrator-service" + cluster = aws_ecs_cluster.fargate_cluster.id + task_definition = aws_ecs_task_definition.orchestrator_service.arn + desired_count = 1 + launch_type = "FARGATE" + + network_configuration { + subnets = local.subnet_ids + security_groups = [data.aws_security_group.default.id] + assign_public_ip = true + } +} + +resource "aws_ecs_service" "vdf_job_service" { + name = "vdf-job-service" + cluster = aws_ecs_cluster.fargate_cluster.id + task_definition = aws_ecs_task_definition.vdf_job.arn + desired_count = 0 + + capacity_provider_strategy { + capacity_provider = "FARGATE_SPOT" + weight = 1 + } + + network_configuration { + subnets = local.subnet_ids + security_groups = [data.aws_security_group.default.id] + assign_public_ip = true + } +} + + diff --git a/terraform/iam_roles.tf b/terraform/iam_roles.tf new file mode 100644 index 0000000..55b055c --- /dev/null +++ b/terraform/iam_roles.tf @@ -0,0 +1,80 @@ +# iam_roles.tf + +data "aws_caller_identity" "current" {} + +resource "aws_iam_role" "task_role" { + name = "orchestrator-task-role" + assume_role_policy = data.aws_iam_policy_document.task_role_policy.json +} + +resource "aws_iam_role" "execution_role" { + name = "orchestrator-execution-role" + assume_role_policy = data.aws_iam_policy_document.execution_role_policy.json +} + +data "aws_iam_policy_document" "task_role_policy" { + statement { + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["ecs-tasks.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "execution_role_policy" { + statement { + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["ecs-tasks.amazonaws.com"] + } + } +} + +# Attach necessary policies to orchestrator-task-role +resource "aws_iam_role_policy" "orchestrator_task_policy" { + name = "orchestrator-task-policy" + role = aws_iam_role.task_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "ecs:RunTask", + "ecs:DescribeTasks", + "ecs:DescribeTaskDefinition", + "ecs:ListTasks" + ] + Resource = [ + "arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:task-definition/vdf-job:*", + "arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:task/${aws_ecs_cluster.fargate_cluster.name}/*" + ] + }, + { + Effect = "Allow" + Action = [ + "iam:PassRole" + ] + Resource = [ + aws_iam_role.execution_role.arn, + aws_iam_role.task_role.arn + ] + } + ] + }) +} + + +# Attach policies to the execution role +resource "aws_iam_role_policy_attachment" "ecs_task_execution" { + role = aws_iam_role.execution_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +resource "aws_iam_role_policy_attachment" "ecs_logs_policy" { + role = aws_iam_role.execution_role.name + policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" +} diff --git a/terraform/logging.tf b/terraform/logging.tf new file mode 100644 index 0000000..5ee8731 --- /dev/null +++ b/terraform/logging.tf @@ -0,0 +1,6 @@ +# logging.tf + +resource "aws_cloudwatch_log_group" "ecs_orchestrator_service" { + name = "ecs-orchestrator-service" + retention_in_days = 30 # Adjust retention as needed +} diff --git a/terraform/networking.tf b/terraform/networking.tf new file mode 100644 index 0000000..f0f9919 --- /dev/null +++ b/terraform/networking.tf @@ -0,0 +1,30 @@ +# Get the default VPC +data "aws_vpc" "default" { + default = true +} + +# Fetch all subnets within the default VPC +data "aws_subnets" "default_vpc_subnets" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } +} + +# Use a local variable to store the subnet IDs +locals { + subnet_ids = data.aws_subnets.default_vpc_subnets.ids +} + +# Get the default security group within the VPC +data "aws_security_group" "default" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } + + filter { + name = "group-name" + values = ["default"] + } +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..b094fab --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,5 @@ +# outputs.tf + +output "postgres_endpoint" { + value = aws_db_instance.postgres.address +} diff --git a/terraform/package.json b/terraform/package.json new file mode 100644 index 0000000..9f6139e --- /dev/null +++ b/terraform/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "ao-process-clients": "^2.3.1" + }, + "devDependencies": { + "@types/node": "^22.9.1" + } +} diff --git a/terraform/posgress.tf b/terraform/posgress.tf new file mode 100644 index 0000000..c7d3ba4 --- /dev/null +++ b/terraform/posgress.tf @@ -0,0 +1,24 @@ +# posgress.tf - Corrected to use db_name for PostgreSQL in AWS RDS + +resource "aws_db_instance" "postgres" { + allocated_storage = 20 + storage_type = "gp2" + engine = "postgres" + engine_version = "13" + instance_class = "db.t3.micro" + db_name = var.db_name # Corrected argument + username = var.db_user + password = var.db_password + skip_final_snapshot = true + publicly_accessible = true # Ensure this matches your security needs + db_subnet_group_name = aws_db_subnet_group.default.name + vpc_security_group_ids = [data.aws_security_group.default.id] +} + +resource "aws_db_subnet_group" "default" { + name = "postgres-subnet-group" + subnet_ids = local.subnet_ids + tags = { + Name = "postgres-subnet-group" + } +} diff --git a/terraform/providers.tf b/terraform/providers.tf new file mode 100644 index 0000000..fad7242 --- /dev/null +++ b/terraform/providers.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.0" + } + } +} + +provider "aws" { + region = "us-east-1" # or your specified region +} diff --git a/terraform/task_definitions.tf b/terraform/task_definitions.tf new file mode 100644 index 0000000..7755465 --- /dev/null +++ b/terraform/task_definitions.tf @@ -0,0 +1,73 @@ +# task_definitions.tf - Updated to Add VDF Job Task and Connect Orchestrator to RDS + +resource "aws_ecs_task_definition" "orchestrator_service" { + family = "orchestrator-service" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = "256" + memory = "512" + +container_definitions = jsonencode([{ + name = "orchestrator" + image = "randao/orchestrator:v0.1.4" + essential = true + environment = [ + { name = "ENVIRONMENT", value = "cloud" }, + { name = "DB_HOST", value = aws_db_instance.postgres.address }, + { name = "DB_PORT", value = "5432" }, + { name = "DB_USER", value = var.db_user }, + { name = "DB_PASSWORD", value = var.db_password }, + { name = "DB_NAME", value = var.db_name }, + { name = "ECS_CLUSTER_NAME", value = aws_ecs_cluster.fargate_cluster.name }, + { name = "SUBNET_ID", value = local.subnet_ids[0] }, # First subnet from your VPC + { name = "SECURITY_GROUP", value = data.aws_security_group.default.id } # Default security group + ] + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = aws_cloudwatch_log_group.ecs_orchestrator_service.name + awslogs-region = var.aws_region + awslogs-stream-prefix = "orchestrator" + } + } +}]) + + + + execution_role_arn = aws_iam_role.execution_role.arn + task_role_arn = aws_iam_role.task_role.arn +} + +resource "aws_ecs_task_definition" "vdf_job" { + family = "vdf-job" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = "256" + memory = "512" + +container_definitions = jsonencode([{ + name = "vdf_job_container" + image = "randao/vdf_job:v0.1.0" + essential = true + command = ["python", "main.py"] # Set the default command to run + environment = [ + { name = "DB_HOST", value = aws_db_instance.postgres.address }, + { name = "DB_PORT", value = "5432" }, + { name = "DB_USER", value = var.db_user }, + { name = "DB_PASSWORD", value = var.db_password }, + { name = "DB_NAME", value = var.db_name } + ] + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = aws_cloudwatch_log_group.ecs_orchestrator_service.name + awslogs-region = var.aws_region + awslogs-stream-prefix = "vdf-job" + } + } +}]) + + + execution_role_arn = aws_iam_role.execution_role.arn + task_role_arn = aws_iam_role.task_role.arn +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..690fe21 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,25 @@ +# variables.tf + +variable "aws_region" { + description = "The AWS region to deploy resources in" + type = string + default = "us-east-1" +} + +variable "db_user" { + description = "The PostgreSQL username" + type = string + default = "myuser" +} + +variable "db_password" { + description = "The PostgreSQL password" + type = string + default = "mypassword" +} + +variable "db_name" { + description = "The PostgreSQL database name" + type = string + default = "mydatabase" +} From acf7e68d640215a791b8cdfd71c7388dc7720fce Mon Sep 17 00:00:00 2001 From: ethan Date: Mon, 25 Nov 2024 13:41:31 -0500 Subject: [PATCH 03/51] added new requester --- docker-compose.yml | 4 ++-- orchestrator/src/app.ts | 3 ++- requester/src/app.ts | 7 ++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9a289c0..b724817 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.1.8 + image: randao/orchestrator:v0.1.10 depends_on: postgres: condition: service_healthy @@ -42,7 +42,7 @@ services: - ./wallet.json:/app/wallet.json # Mount wallet.json into the container requester: - image: randao/requester:v0.1.1 + image: randao/requester:v0.1.5 environment: PATH_TO_WALLET: /app/wallet.json # Path inside the container REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index b0d65c0..4c39dfa 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -24,7 +24,7 @@ const dbConfig = { }; // Constants for configuration -const POLLING_INTERVAL_MS = 500; +const POLLING_INTERVAL_MS = 5000; const MINIMUM_ENTRIES = 50; const TARGET_ENTRIES = 75; const DROP_CHANCE = 0.005; @@ -263,6 +263,7 @@ async function polling(client: Client): Promise { console.log(PROVIDER_ID); const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); + console.log("here") console.log(openRequests); if (openRequests && openRequests.activeRequests && openRequests.activeRequests.request_ids) { diff --git a/requester/src/app.ts b/requester/src/app.ts index 5e609ca..1e98b18 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -6,8 +6,8 @@ import { } from "ao-process-clients"; const PROVIDER_ID = "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE"; -const RETRY_DELAY_MS = 500; -const CHANCE_TO_CALL_RANDOM = 0.05; +const RETRY_DELAY_MS = 5000; +const CHANCE_TO_CALL_RANDOM = 1; const RANDOM_CONFIG: RandomClientConfig = { tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, @@ -25,6 +25,7 @@ async function main() { const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG); while (true) { + console.log("Running") try { // Roll for random chance to make a request if (Math.random() < CHANCE_TO_CALL_RANDOM) { @@ -93,4 +94,4 @@ function delay(ms: number): Promise { } // Call the main function -main(); +main(); \ No newline at end of file From 16790cce31410f6b18e51de1175257ff55257dc3 Mon Sep 17 00:00:00 2001 From: ethan Date: Mon, 25 Nov 2024 15:57:40 -0500 Subject: [PATCH 04/51] added new requester --- orchestrator/docs/development.md | 5 +++++ requester/docs/development.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index e69de29..e1a2d70 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -0,0 +1,5 @@ +To build: + +Save all files +Run: +docker build -t randao/orchestrator:latest -t randao/orchestrator:v0.1.10 . \ No newline at end of file diff --git a/requester/docs/development.md b/requester/docs/development.md index e69de29..5cb89cb 100644 --- a/requester/docs/development.md +++ b/requester/docs/development.md @@ -0,0 +1,5 @@ +To build: + +Save all files +Run: +docker build -t randao/requester:latest -t randao/requester:v0.1.5 . \ No newline at end of file From da094d967956268c83596636c8eab7423af830a6 Mon Sep 17 00:00:00 2001 From: ethan Date: Mon, 25 Nov 2024 16:02:20 -0500 Subject: [PATCH 05/51] added new requester --- .env.example | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9d969ba --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +DB_USER=myuser +DB_PASSWORD=mypassword +DB_NAME=mydatabase +DOCKER_NETWORK=backend +PATH_TO_WALLET="wallet.json" +PROVIDER_ID = "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE" +WALLET_JSON = '{}' +REQUEST_WALLET_JSON = '{}' \ No newline at end of file From 0a23de433ee0886ae6531333ab506391a7306590 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 25 Nov 2024 18:17:08 -0500 Subject: [PATCH 06/51] orch now fuffils requests --- docker-compose.yml | 24 +++++------ orchestrator/src/app.ts | 91 ++++++++++++++++++++++++++--------------- 2 files changed, 71 insertions(+), 44 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b724817..322fe19 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.1.10 + image: randao/orchestrator:v0.1.16 depends_on: postgres: condition: service_healthy @@ -41,17 +41,17 @@ services: - /var/run/docker.sock:/var/run/docker.sock # Mount Docker socket - ./wallet.json:/app/wallet.json # Mount wallet.json into the container - requester: - image: randao/requester:v0.1.5 - environment: - PATH_TO_WALLET: /app/wallet.json # Path inside the container - REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} - PROVIDER_ID: ${PROVIDER_ID} - DOCKER_NETWORK: backend # Passing the network name - networks: - - backend - volumes: - - ./wallet.json:/app/wallet.json # Mount wallet.json into the container + # requester: + # image: randao/requester:v0.1.5 + # environment: + # PATH_TO_WALLET: /app/wallet.json # Path inside the container + # REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} + # PROVIDER_ID: ${PROVIDER_ID} + # DOCKER_NETWORK: backend # Passing the network name + # networks: + # - backend + # volumes: + # - ./wallet.json:/app/wallet.json # Mount wallet.json into the container networks: backend: diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 4c39dfa..b80e888 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -28,11 +28,12 @@ const POLLING_INTERVAL_MS = 5000; const MINIMUM_ENTRIES = 50; const TARGET_ENTRIES = 75; const DROP_CHANCE = 0.005; -//Expected increments per second=10×0.005=0.05 +//Expected increments per second=10×0.005=0.05 //180 times per hour //4,320 times per day //1,576,800 times per year const MAX_OUTSTANDING_REQUESTS = 10; +const MAX_OUTSTANDING_FULFILLMENTS = 10; const MAX_RETRIES = 10; const RETRY_DELAY_MS = 10000; const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.0'; @@ -40,11 +41,10 @@ const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks const ongoingContainers = new Set(); // Track container IDs of running Docker containers +const ongoingFulfillments = new Set(); // Track request IDs for ongoing fulfillments const PROVIDER_ID = process.env.PROVIDER_ID || "0"; - - - let ongoingRequest = false; +let spotInterruptions = 0; // Retry logic for connecting to PostgreSQL async function connectWithRetry(): Promise { @@ -78,8 +78,6 @@ async function setupDatabase(client: Client): Promise { console.log("Database setup complete. 'verifiable_delay_functions' table is ready."); } -// Added variable to track the number of Spot instance interruptions -let spotInterruptions = 0; // Modified function to trigger VDF job pod using ECS or Docker async function triggerVDFJobPod(): Promise { @@ -195,8 +193,6 @@ async function monitorECSTasks(): Promise { } - - // Function to wait for Docker containers to complete and remove them from tracking async function monitorDockerContainers(): Promise { if (ongoingContainers.size === 0) return; @@ -254,8 +250,43 @@ async function checkAndFetchIfNeeded(client: Client): Promise { ongoingRequest = false; } } +// Function to post VDF challenge and proof +async function fulfillRandomRequest(client: Client, dbId: string, requestId: string, modulus: string, input: string): Promise { + if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) return; + ongoingFulfillments.add(dbId); + try { + console.log(`Posting VDF challenge for entry ID: ${dbId}, request ID: ${requestId}`); + await randclient.postVDFChallenge(requestId, modulus, input); + console.log(`Challenge posted for request ID: ${requestId}. Waiting to post proof...`); + + await new Promise(resolve => setTimeout(resolve, 5000)); + + // Fetch the output and proof from the database + const res = await client.query('SELECT output, proof FROM verifiable_delay_functions WHERE id = $1', [dbId]); + if (!res.rowCount) { + console.error(`No entry found for ID: ${dbId}`); + return; + } + const { output, proof } = res.rows[0]; -// Polling function to manage entries and delete old ones occasionally + // Stringify the proof if it is an array + const proofString = Array.isArray(proof) ? JSON.stringify(proof) : proof; + + console.log(`Posting VDF output and proof for request ID: ${requestId}`); + await randclient.postVDFOutputAndProof(requestId, output, proofString); + console.log(`Proof posted for request ID: ${requestId}`); + + // Delete the entry from the database after successfully posting the proof + await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [dbId]); + console.log(`Entry with ID: ${dbId} deleted from database.`); + } catch (error) { + console.error(`Error fulfilling random request for entry ID: ${dbId}, request ID: ${requestId}:`, error); + } finally { + ongoingFulfillments.delete(dbId); + } +} + +// Modified polling function to fulfill open requests if they exist async function polling(client: Client): Promise { await checkAndFetchIfNeeded(client); @@ -263,7 +294,7 @@ async function polling(client: Client): Promise { console.log(PROVIDER_ID); const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); - console.log("here") + console.log("here"); console.log(openRequests); if (openRequests && openRequests.activeRequests && openRequests.activeRequests.request_ids) { @@ -271,34 +302,32 @@ async function polling(client: Client): Promise { console.log(openRequests.activeRequests); console.log(openRequests.activeRequests.request_ids); console.log(openRequests.activeRequests.request_ids.length); - } else { - console.log('No requests'); - } - } catch (error) { - console.error('An error occurred while fetching open random requests:', error); - } + // Check if there are enough entries in the database to fulfill the requests + const res = await client.query('SELECT * FROM verifiable_delay_functions ORDER BY id ASC LIMIT $1', [openRequests.activeRequests.request_ids.length]); - if (Math.random() < DROP_CHANCE) { - try { - console.log("Randomly chosen to log and delete the oldest entry..."); - const res = await client.query('SELECT * FROM verifiable_delay_functions ORDER BY id ASC LIMIT 1'); - const entry = res.rows[0]; - - if (entry) { - console.log("Logging and deleting oldest entry:", JSON.stringify(entry, null, 2)); - await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [entry.id]); - console.log("Oldest entry deleted from database."); + if (res.rowCount !== null && res.rowCount > 0) { + const rowsToProcess = Math.min(res.rowCount, openRequests.activeRequests.request_ids.length); + + for (let i = 0; i < rowsToProcess; i++) { + if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) break; + + const { id, modulus, input } = res.rows[i]; + const requestId = openRequests.activeRequests.request_ids[i]; + fulfillRandomRequest(client, id, requestId, modulus, input); + } } else { - console.log("No entries available to delete."); + console.log('Not enough entries in the database to fulfill all requests.'); } - } catch (error) { - console.error('Error logging and deleting oldest entry:', error); + } else { + console.log('No requests'); } - } else { + } catch (error) { + console.error('An error occurred while fetching open random requests:', error); } } + // Main function async function run(): Promise { const client = await connectWithRetry(); @@ -313,8 +342,6 @@ async function run(): Promise { await polling(client); }, POLLING_INTERVAL_MS); - - process.on("SIGTERM", async () => { console.log("SIGTERM received. Closing database connection."); await client.end(); From 100f34976c48d4546e8a861dd6beb453e85bf9ba Mon Sep 17 00:00:00 2001 From: Ethan Date: Sun, 8 Dec 2024 16:57:24 -0500 Subject: [PATCH 07/51] changed loops --- docker-compose.yml | 4 +- orchestrator/package.json | 4 +- orchestrator/src/app.ts | 164 ++++++--- orchestrator/src/test.ts | 51 --- requester/orchestrator/.dockerignore | 3 - requester/orchestrator/Dockerfile | 23 -- requester/orchestrator/README.md | 6 - requester/orchestrator/docs/development.md | 0 requester/orchestrator/package.json | 27 -- requester/orchestrator/src/app.ts | 313 ------------------ requester/orchestrator/src/test.ts | 51 --- requester/orchestrator/tsconfig.json | 16 - requester/package.json | 2 +- requester/src/app.ts | 83 ++--- .../src/protocol_constants.py | 2 +- 15 files changed, 173 insertions(+), 576 deletions(-) delete mode 100644 orchestrator/src/test.ts delete mode 100644 requester/orchestrator/.dockerignore delete mode 100644 requester/orchestrator/Dockerfile delete mode 100644 requester/orchestrator/README.md delete mode 100644 requester/orchestrator/docs/development.md delete mode 100644 requester/orchestrator/package.json delete mode 100644 requester/orchestrator/src/app.ts delete mode 100644 requester/orchestrator/src/test.ts delete mode 100644 requester/orchestrator/tsconfig.json diff --git a/docker-compose.yml b/docker-compose.yml index 322fe19..4063202 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.1.16 + image: randao/orchestrator:v0.1.50 depends_on: postgres: condition: service_healthy @@ -42,7 +42,7 @@ services: - ./wallet.json:/app/wallet.json # Mount wallet.json into the container # requester: - # image: randao/requester:v0.1.5 + # image: randao/requester:v0.1.6 # environment: # PATH_TO_WALLET: /app/wallet.json # Path inside the container # REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/orchestrator/package.json b/orchestrator/package.json index 01f8b49..67bb916 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "^2.3.2", + "ao-process-clients": "^3", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", @@ -24,4 +24,4 @@ "keywords": [], "author": "", "license": "ISC" -} \ No newline at end of file +} diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index b80e888..31fd2ea 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -36,7 +36,7 @@ const MAX_OUTSTANDING_REQUESTS = 10; const MAX_OUTSTANDING_FULFILLMENTS = 10; const MAX_RETRIES = 10; const RETRY_DELAY_MS = 10000; -const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.0'; +const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.1'; const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks @@ -45,7 +45,7 @@ const ongoingFulfillments = new Set(); // Track request IDs for ongoing const PROVIDER_ID = process.env.PROVIDER_ID || "0"; let ongoingRequest = false; let spotInterruptions = 0; - +let totalProvided = 0; // Retry logic for connecting to PostgreSQL async function connectWithRetry(): Promise { const client = new Client(dbConfig); @@ -64,6 +64,13 @@ async function connectWithRetry(): Promise { // Setup the `verifiable_delay_functions` table if not exists async function setupDatabase(client: Client): Promise { + + // // Drop the table if it exists + // await client.query(` + // DROP TABLE IF EXISTS verifiable_delay_functions; + // `); + // console.log("'verifiable_delay_functions' table dropped."); + await client.query(` CREATE TABLE IF NOT EXISTS verifiable_delay_functions ( id TEXT PRIMARY KEY, -- Define as TEXT to match UUID format @@ -218,9 +225,10 @@ async function monitorDockerContainers(): Promise { async function checkAndFetchIfNeeded(client: Client): Promise { try { if (ongoingRequest) return; + const res = await client.query('SELECT COUNT(*) AS count FROM verifiable_delay_functions WHERE request_id IS NULL'); + const currentCount = parseInt(res.rows[0].count, 10); // Parse the count as an integer + console.log("Total usable db entries: " + currentCount); - const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); - const currentCount = parseInt(res.rows[0].count, 10); const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(currentCount); console.log("Updates onchain: " + updateAvailableValuesResult) console.log(await randclient.getProviderAvailableValues(PROVIDER_ID)) @@ -250,25 +258,66 @@ async function checkAndFetchIfNeeded(client: Client): Promise { ongoingRequest = false; } } -// Function to post VDF challenge and proof -async function fulfillRandomRequest(client: Client, dbId: string, requestId: string, modulus: string, input: string): Promise { + +// Function to clear all output requests +async function clearAllOutputRequests(client: Client): Promise { + try { + // Fetch the current open output requests + const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); + console.log("Open requests fetched:", openRequests); + + if (openRequests && openRequests.activeOutputRequests) { + openRequests.activeOutputRequests.request_ids.forEach((requestId) => { + console.log(`Sending "No data" for output request ID: ${requestId}`); + + // Send "No data" as output for each request (do not await, fire and forget) + randclient.postVDFOutputAndProof(requestId, "No data", "No data").then(() => { + console.log(`"No data" sent for request ID: ${requestId}`); + }).catch((error) => { + console.error(`Error sending "No data" for request ID: ${requestId}:`, error); + }); + }); + } else { + console.log('No Output requests to clear'); + } + } catch (error) { + console.error('An error occurred while clearing output requests:', error); + } +} + + +// Function to post VDF challenge +async function fulfillRandomChallenge(client: Client, dbId: string, requestId: string, modulus: string, input: string): Promise { if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) return; - ongoingFulfillments.add(dbId); + try { + // Assume dbId, requestId, modulus, and input are passed in as parameters + + console.log(`Received entry details - ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); + + console.log(`Posting VDF challenge for entry ID: ${dbId}, request ID: ${requestId}`); await randclient.postVDFChallenge(requestId, modulus, input); console.log(`Challenge posted for request ID: ${requestId}. Waiting to post proof...`); - - await new Promise(resolve => setTimeout(resolve, 5000)); - - // Fetch the output and proof from the database - const res = await client.query('SELECT output, proof FROM verifiable_delay_functions WHERE id = $1', [dbId]); + } catch (error) { + console.error(`Error posting VDF challenge for request ID: ${requestId}:`, error); + } +} + + +// Function to post VDF output and proof +async function fulfillRandomOutput(client: Client, requestId: string): Promise { + try { + // Fetch the output and proof from the database using the requestId + const res = await client.query('SELECT id, output, proof FROM verifiable_delay_functions WHERE request_id = $1', [requestId]); if (!res.rowCount) { - console.error(`No entry found for ID: ${dbId}`); + console.error(`No entry found for request ID: ${requestId}`); return; } - const { output, proof } = res.rows[0]; + const { id: dbId, output, proof } = res.rows[0]; + // console.log(`Fetched entry from database for output - ID: ${dbId}, Output: ${output}, Proof: ${proof}`); + console.log(`Fetched entry from database for output - ID: ${dbId}, requestID: ${requestId}`); // Stringify the proof if it is an array const proofString = Array.isArray(proof) ? JSON.stringify(proof) : proof; @@ -276,51 +325,87 @@ async function fulfillRandomRequest(client: Client, dbId: string, requestId: str await randclient.postVDFOutputAndProof(requestId, output, proofString); console.log(`Proof posted for request ID: ${requestId}`); - // Delete the entry from the database after successfully posting the proof - await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [dbId]); - console.log(`Entry with ID: ${dbId} deleted from database.`); - } catch (error) { - console.error(`Error fulfilling random request for entry ID: ${dbId}, request ID: ${requestId}:`, error); - } finally { ongoingFulfillments.delete(dbId); + } catch (error) { + console.error(`Error fulfilling random output for request ID: ${requestId}:`, error); } } // Modified polling function to fulfill open requests if they exist async function polling(client: Client): Promise { + console.log("Starting Polling...") await checkAndFetchIfNeeded(client); try { console.log(PROVIDER_ID); + // Part 1: Fetch open requests const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); - console.log("here"); - console.log(openRequests); - - if (openRequests && openRequests.activeRequests && openRequests.activeRequests.request_ids) { - console.log(openRequests.providerId); - console.log(openRequests.activeRequests); - console.log(openRequests.activeRequests.request_ids); - console.log(openRequests.activeRequests.request_ids.length); + console.log("Open requests fetched:", openRequests); + if (false) { + clearAllOutputRequests(client) + } + else { - // Check if there are enough entries in the database to fulfill the requests - const res = await client.query('SELECT * FROM verifiable_delay_functions ORDER BY id ASC LIMIT $1', [openRequests.activeRequests.request_ids.length]); - if (res.rowCount !== null && res.rowCount > 0) { - const rowsToProcess = Math.min(res.rowCount, openRequests.activeRequests.request_ids.length); - for (let i = 0; i < rowsToProcess; i++) { + // Part 2: Fulfill Challenge requests + if (openRequests && openRequests.activeChallengeRequests) { + for (const requestId of openRequests.activeChallengeRequests.request_ids) { if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) break; + try { + // Fetch modulus and input from the database + const res = await client.query('SELECT * FROM verifiable_delay_functions WHERE request_id IS NULL ORDER BY id ASC LIMIT 1'); + if (!res.rowCount) { + console.error(`No available entry found in the database.`); + return; + } + const { id: dbId, modulus, input } = res.rows[0]; + + // console.log(`Fetched entry from database - ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); + console.log(`Fetched entry from database - ID: ${dbId}, RequestID: ${requestId}`); + // Update the database to save the requestId with the entry + await client.query('UPDATE verifiable_delay_functions SET request_id = $1 WHERE id = $2', [requestId, dbId]); + console.log(`Updated database entry with request ID: ${requestId} for entry ID: ${dbId}`); + ongoingFulfillments.add(dbId); + console.log(`Processing challenge request ID: ${requestId}`); + fulfillRandomChallenge(client, dbId, requestId, modulus, input); + } catch (error) { + console.log(error) + } + } + } else { + console.log('No Challenge requests'); + } - const { id, modulus, input } = res.rows[i]; - const requestId = openRequests.activeRequests.request_ids[i]; - fulfillRandomRequest(client, id, requestId, modulus, input); + // Part 3: Fulfill Output requests + if (openRequests && openRequests.activeOutputRequests) { + for (const requestId of openRequests.activeOutputRequests.request_ids) { + console.log(`Processing output request ID: ${requestId}`); + fulfillRandomOutput(client, requestId); } } else { - console.log('Not enough entries in the database to fulfill all requests.'); + console.log('No Output requests'); + } + + // Part 4: Check for completed fulfillments that are no longer in the challenge or output list + for (const ongoingId of ongoingFulfillments) { + const challengeInProgress = openRequests.activeChallengeRequests?.request_ids.includes(ongoingId); + const outputInProgress = openRequests.activeOutputRequests?.request_ids.includes(ongoingId); + + console.log(`Checking ID: ${ongoingId}`); + console.log(` - In active challenges: ${challengeInProgress}`); + console.log(` - In active outputs: ${outputInProgress}`); + + if (!challengeInProgress && !outputInProgress) { + console.log(`Entry with ID: ${ongoingId} is no longer requested.`); + // await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [ongoingId]); + // console.log(`Entry with ID: ${ongoingId} deleted from database.`); + // ongoingFulfillments.delete(ongoingId); + // totalProvided++; + // console.log("total provided = " + totalProvided); + } } - } else { - console.log('No requests'); } } catch (error) { console.error('An error occurred while fetching open random requests:', error); @@ -328,6 +413,7 @@ async function polling(client: Client): Promise { } + // Main function async function run(): Promise { const client = await connectWithRetry(); diff --git a/orchestrator/src/test.ts b/orchestrator/src/test.ts deleted file mode 100644 index 2b56b81..0000000 --- a/orchestrator/src/test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { IRandomClient, RandomClient } from "ao-process-clients"; - -const PROVIDER_ID = process.env.PROVIDER_ID || "0"; - -async function main() { - try { - const randclient: IRandomClient = RandomClient.autoConfiguration(); - - console.log("Testing `createRequest`..."); - const createRequestResult = await randclient.createRequest(["provider1", "provider2"]); - console.log("createRequest result:", createRequestResult); - - console.log("Testing `getOpenRandomRequests`..."); - const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); - console.log("getOpenRandomRequests result:", openRequests); - - console.log("Testing `getProviderAvailableValues`..."); - const availableValues = await randclient.getProviderAvailableValues(PROVIDER_ID); - console.log("getProviderAvailableValues result:", availableValues); - - console.log("Testing `getRandomRequests`..."); - const randomRequests = await randclient.getRandomRequests(["request1", "request2"]); - console.log("getRandomRequests result:", randomRequests); - - console.log("Testing `postVDFChallenge`..."); - const postVDFChallengeResult = await randclient.postVDFChallenge( - "request1", - "modulus_value", - "input_value" - ); - console.log("postVDFChallenge result:", postVDFChallengeResult); - - console.log("Testing `postVDFOutputAndProof`..."); - const postVDFOutputAndProofResult = await randclient.postVDFOutputAndProof( - "request1", - "output_value", - "proof_value" - ); - console.log("postVDFOutputAndProof result:", postVDFOutputAndProofResult); - - console.log("Testing `updateProviderAvailableValues`..."); - const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(42); - console.log("updateProviderAvailableValues result:", updateAvailableValuesResult); - - } catch (error) { - console.error("An error occurred during testing:", error); - } -} - -// Call the main function -main(); diff --git a/requester/orchestrator/.dockerignore b/requester/orchestrator/.dockerignore deleted file mode 100644 index a0bedda..0000000 --- a/requester/orchestrator/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -.git -dist diff --git a/requester/orchestrator/Dockerfile b/requester/orchestrator/Dockerfile deleted file mode 100644 index e0b8740..0000000 --- a/requester/orchestrator/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# Use the official Node.js image as the base image -FROM node:20 - -# Create and set the working directory -WORKDIR /usr/src/app - -# Copy package.json and package-lock.json -COPY package*.json ./ - -# Install dependencies -RUN npm install - -# Copy the entire project into the container -COPY . . - -# Compile TypeScript to JavaScript -RUN npx tsc - -# Expose the port if needed -EXPOSE 3000 - -# Run the compiled app -CMD ["node", "dist/app.js"] diff --git a/requester/orchestrator/README.md b/requester/orchestrator/README.md deleted file mode 100644 index 5470a55..0000000 --- a/requester/orchestrator/README.md +++ /dev/null @@ -1,6 +0,0 @@ -cd into repo - -docker login -docker build -t randao/orchestrator:latest -t randao/orchestrator:v0.1.4 . -docker push satoshispalace/orchestrator:v0.1.4 -docker push satoshispalace/orchestrator:latest diff --git a/requester/orchestrator/docs/development.md b/requester/orchestrator/docs/development.md deleted file mode 100644 index e69de29..0000000 diff --git a/requester/orchestrator/package.json b/requester/orchestrator/package.json deleted file mode 100644 index 01f8b49..0000000 --- a/requester/orchestrator/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "devDependencies": { - "@types/dockerode": "^3.3.31", - "@types/node": "^22.9.1", - "@types/pg": "^8.11.10", - "serverless-offline": "^14.3.3", - "typescript": "^5.6.3" - }, - "dependencies": { - "ao-process-clients": "^2.3.2", - "aws-sdk": "^2.1692.0", - "axios": "^1.7.7", - "crypto": "^1.0.1", - "dockerode": "^4.0.2", - "pg": "^8.13.1" - }, - "name": "ao-vrf", - "description": "1. To build:\r ```\r docker build -t serverless-multi-cloud .\r ```", - "version": "1.0.0", - "main": "Organizer.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC" -} \ No newline at end of file diff --git a/requester/orchestrator/src/app.ts b/requester/orchestrator/src/app.ts deleted file mode 100644 index 33c4cab..0000000 --- a/requester/orchestrator/src/app.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { Client } from 'pg'; -import Docker from 'dockerode'; -import AWS from 'aws-sdk'; -import { getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomClientConfig } from "ao-process-clients" - -// const RANDOM_CONFIG: RandomClientConfig = { -// tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, -// processId: getRandomClientAutoConfiguration().processId, -// wallet: JSON.parse(process.env.WALLET_JSON!), -// environment: 'mainnet' -// } -const randclient: IRandomClient = RandomClient.autoConfiguration() -//const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG) - -const docker = new Docker(); - -// Database configuration -const dbConfig = { - host: process.env.DB_HOST || 'localhost', - port: parseInt(process.env.DB_PORT || '5432', 10), - user: process.env.DB_USER || 'myuser', - password: process.env.DB_PASSWORD || 'mypassword', - database: process.env.DB_NAME || 'mydatabase', -}; - -// Constants for configuration -const POLLING_INTERVAL_MS = 100; -const MINIMUM_ENTRIES = 50; -const TARGET_ENTRIES = 75; -const DROP_CHANCE = 0.005; -//Expected increments per second=10×0.005=0.05 -//180 times per hour -//4,320 times per day -//1,576,800 times per year -const MAX_OUTSTANDING_REQUESTS = 10; -const MAX_RETRIES = 10; -const RETRY_DELAY_MS = 10000; -const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.0'; -const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; -const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); -const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks -const ongoingContainers = new Set(); // Track container IDs of running Docker containers -const PROVIDER_ID = process.env.PROVIDER_ID || "0"; - - - -let ongoingRequest = false; - -// Retry logic for connecting to PostgreSQL -async function connectWithRetry(): Promise { - const client = new Client(dbConfig); - for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { - try { - await client.connect(); - console.log(`Connected to PostgreSQL database (Attempt ${attempt})`); - return client; - } catch (error) { - console.error(`Connection attempt ${attempt} failed, retrying in ${RETRY_DELAY_MS / 1000} seconds...`); - await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS)); - } - } - throw new Error("Failed to connect to PostgreSQL after multiple attempts"); -} - -// Setup the `verifiable_delay_functions` table if not exists -async function setupDatabase(client: Client): Promise { - await client.query(` - CREATE TABLE IF NOT EXISTS verifiable_delay_functions ( - id TEXT PRIMARY KEY, -- Define as TEXT to match UUID format - request_id TEXT, - modulus TEXT NOT NULL, - input TEXT NOT NULL, - output TEXT NOT NULL, - proof JSON NOT NULL, - date TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - `); - console.log("Database setup complete. 'verifiable_delay_functions' table is ready."); -} - -// Added variable to track the number of Spot instance interruptions -let spotInterruptions = 0; - -// Modified function to trigger VDF job pod using ECS or Docker -async function triggerVDFJobPod(): Promise { - if (ENVIRONMENT === 'cloud') { - try { - console.log("Cloud environment detected. Launching ECS task."); - const result = await ecs.runTask({ - cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', - taskDefinition: 'vdf-job', - capacityProviderStrategy: [ - { - capacityProvider: 'FARGATE_SPOT', - weight: 1 - } - ], - networkConfiguration: { - awsvpcConfiguration: { - subnets: [process.env.SUBNET_ID || 'subnet-12345678'], - securityGroups: [process.env.SECURITY_GROUP || 'sg-12345678'], - assignPublicIp: 'ENABLED' - } - }, - overrides: { - containerOverrides: [ - { - name: 'vdf_job_container', - environment: [ - { name: 'DATABASE_TYPE', value: 'postgresql' }, - { name: 'DATABASE_HOST', value: process.env.DB_HOST || 'cloud-postgres-host' }, - { name: 'DATABASE_PORT', value: process.env.DB_PORT || '5432' }, - { name: 'DATABASE_USER', value: process.env.DB_USER || 'myuser' }, - { name: 'DATABASE_PASSWORD', value: process.env.DB_PASSWORD || 'mypassword' }, - { name: 'DATABASE_NAME', value: process.env.DB_NAME || 'mydatabase' }, - ] - } - ] - }, - count: 1 - }).promise(); - - const taskArn = result.tasks?.[0]?.taskArn; - if (taskArn) { - ongoingTasks.add(taskArn); - console.log(`ECS task started successfully: ${taskArn}`); - return taskArn; - } - return null; - } catch (error: any) { - if (error?.code === 'SpotCapacityNotAvailableException') { - spotInterruptions++; - console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); - } else { - console.error("Error launching ECS task:", error); - } - return null; - } - } else { - const containerName = `vdf_job_${Date.now()}`; - console.log(`Starting Docker container with name: ${containerName}`); - const container = await docker.createContainer({ - Image: VDF_JOB_IMAGE, - Cmd: ['python', 'main.py'], - Env: [ - `DATABASE_TYPE=postgresql`, - `DATABASE_HOST=${dbConfig.host}`, - `DATABASE_PORT=${dbConfig.port.toString()}`, - `DATABASE_USER=${dbConfig.user}`, - `DATABASE_PASSWORD=${dbConfig.password}`, - `DATABASE_NAME=${dbConfig.database}`, - ], - HostConfig: { - NetworkMode: 'backend', - }, - name: containerName - }); - - await container.start(); - ongoingContainers.add(container.id); - console.log(`Docker container ${containerName} started successfully.`); - return container.id; - } -} - -// Modified function to wait for ECS tasks to complete and remove them from tracking -async function monitorECSTasks(): Promise { - if (ongoingTasks.size === 0) return; - - const describeTasksResult = await ecs.describeTasks({ - cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', - tasks: Array.from(ongoingTasks) - }).promise(); - - describeTasksResult.tasks?.forEach(task => { - // Check capacity provider name to determine if it is running Fargate or Fargate Spot - const capacityProvider = task.capacityProviderName || 'Unknown'; - - console.log(`ECS task: ${task.taskArn}, Capacity Provider: ${capacityProvider}`); - - if (task.lastStatus === 'STOPPED') { - console.log(`ECS task stopped: ${task.taskArn}`); - if (task.stoppedReason) { - console.log(`Task stopped reason: ${task.stoppedReason}`); - if (task.stoppedReason.includes('Host EC2 instance termination')) { - spotInterruptions++; - console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); - } - } else { - console.log('Task stopped due to normal completion.'); - } - ongoingTasks.delete(task.taskArn as string); - } - }); -} - - - - -// Function to wait for Docker containers to complete and remove them from tracking -async function monitorDockerContainers(): Promise { - if (ongoingContainers.size === 0) return; - - for (const containerId of ongoingContainers) { - try { - const container = docker.getContainer(containerId); - const containerInfo = await container.inspect(); - - if (containerInfo.State.Status === 'exited') { - console.log(`Docker container stopped: ${containerId}`); - await container.remove(); - ongoingContainers.delete(containerId); - } - } catch (error) { - console.error(`Error monitoring Docker container ${containerId}:`, error); - ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) - } - } -} - -// Function to check if there are fewer than MINIMUM_ENTRIES and fetch until TARGET_ENTRIES -async function checkAndFetchIfNeeded(client: Client): Promise { - try { - if (ongoingRequest) return; - - const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); - const currentCount = parseInt(res.rows[0].count, 10); - const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(currentCount); - console.log("Updates onchain: " + updateAvailableValuesResult) - console.log(await randclient.getProviderAvailableValues(PROVIDER_ID)) - if (currentCount < MINIMUM_ENTRIES) { - const entriesNeeded = TARGET_ENTRIES - currentCount; - console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries to reach ${TARGET_ENTRIES}...`); - ongoingRequest = true; - let tasksTriggered = 0; - - while (tasksTriggered < entriesNeeded) { - if (ENVIRONMENT === 'cloud' && ongoingTasks.size >= MAX_OUTSTANDING_REQUESTS) { - await monitorECSTasks(); - } else if (ENVIRONMENT !== 'cloud' && ongoingContainers.size >= MAX_OUTSTANDING_REQUESTS) { - await monitorDockerContainers(); - } else { - const taskArn = await triggerVDFJobPod(); - if (taskArn) { - tasksTriggered++; - console.log(`Task triggered: ${taskArn}. Total ongoing tasks: ${ongoingTasks.size}. Total ongoing containers ${+ ongoingContainers.size}`); - } - } - } - ongoingRequest = false; - } - } catch (error) { - console.error('Error during check and fetch:', error); - ongoingRequest = false; - } -} - -// Polling function to manage entries and delete old ones occasionally -async function polling(client: Client): Promise { - await checkAndFetchIfNeeded(client); - - console.log(PROVIDER_ID) - var openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID) - console.log(openRequests) - console.log(openRequests.providerId) - console.log(openRequests.activeRequests) - console.log(openRequests.activeRequests.request_ids) - console.log(openRequests.activeRequests.request_ids.length) - - if (Math.random() < DROP_CHANCE) { - try { - console.log("Randomly chosen to log and delete the oldest entry..."); - const res = await client.query('SELECT * FROM verifiable_delay_functions ORDER BY id ASC LIMIT 1'); - const entry = res.rows[0]; - - if (entry) { - console.log("Logging and deleting oldest entry:", JSON.stringify(entry, null, 2)); - await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [entry.id]); - console.log("Oldest entry deleted from database."); - } else { - console.log("No entries available to delete."); - } - } catch (error) { - console.error('Error logging and deleting oldest entry:', error); - } - } else { - } -} - -// Main function -async function run(): Promise { - const client = await connectWithRetry(); - await setupDatabase(client); - - setInterval(async () => { - const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); - console.log(`Periodic log - Current database size: ${res.rows[0].count}`); - }, 10000); - - setInterval(async () => { - await polling(client); - }, POLLING_INTERVAL_MS); - - - - process.on("SIGTERM", async () => { - console.log("SIGTERM received. Closing database connection."); - await client.end(); - process.exit(0); - }); -} - -run().catch((err) => console.error(`Error in main function: ${err}`)); diff --git a/requester/orchestrator/src/test.ts b/requester/orchestrator/src/test.ts deleted file mode 100644 index 2b56b81..0000000 --- a/requester/orchestrator/src/test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { IRandomClient, RandomClient } from "ao-process-clients"; - -const PROVIDER_ID = process.env.PROVIDER_ID || "0"; - -async function main() { - try { - const randclient: IRandomClient = RandomClient.autoConfiguration(); - - console.log("Testing `createRequest`..."); - const createRequestResult = await randclient.createRequest(["provider1", "provider2"]); - console.log("createRequest result:", createRequestResult); - - console.log("Testing `getOpenRandomRequests`..."); - const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); - console.log("getOpenRandomRequests result:", openRequests); - - console.log("Testing `getProviderAvailableValues`..."); - const availableValues = await randclient.getProviderAvailableValues(PROVIDER_ID); - console.log("getProviderAvailableValues result:", availableValues); - - console.log("Testing `getRandomRequests`..."); - const randomRequests = await randclient.getRandomRequests(["request1", "request2"]); - console.log("getRandomRequests result:", randomRequests); - - console.log("Testing `postVDFChallenge`..."); - const postVDFChallengeResult = await randclient.postVDFChallenge( - "request1", - "modulus_value", - "input_value" - ); - console.log("postVDFChallenge result:", postVDFChallengeResult); - - console.log("Testing `postVDFOutputAndProof`..."); - const postVDFOutputAndProofResult = await randclient.postVDFOutputAndProof( - "request1", - "output_value", - "proof_value" - ); - console.log("postVDFOutputAndProof result:", postVDFOutputAndProofResult); - - console.log("Testing `updateProviderAvailableValues`..."); - const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(42); - console.log("updateProviderAvailableValues result:", updateAvailableValuesResult); - - } catch (error) { - console.error("An error occurred during testing:", error); - } -} - -// Call the main function -main(); diff --git a/requester/orchestrator/tsconfig.json b/requester/orchestrator/tsconfig.json deleted file mode 100644 index 493e143..0000000 --- a/requester/orchestrator/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist", - "rootDir": "./src", - "module": "commonjs", - "target": "es6", - "strict": true, - "esModuleInterop": true - }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/requester/package.json b/requester/package.json index 01f8b49..68a050e 100644 --- a/requester/package.json +++ b/requester/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "^2.3.2", + "ao-process-clients": "^3", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/requester/src/app.ts b/requester/src/app.ts index 1e98b18..3b71834 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -30,56 +30,57 @@ async function main() { // Roll for random chance to make a request if (Math.random() < CHANCE_TO_CALL_RANDOM) { console.log("Initiating random request..."); - await randclient.createRequest([PROVIDER_ID]); + const callbackId = `callback-${Date.now()}`; + await randclient.createRequest([PROVIDER_ID], 1, callbackId); totalRandomCalled++; console.log("Random request initiated. Awaiting request ID in open requests..."); } - // Check open requests - const openRequestsResponse = await randclient.getOpenRandomRequests(PROVIDER_ID); - const openRequestIds = openRequestsResponse.activeRequests.request_ids || []; - console.log("Open requests:", openRequestIds); + // // Check open requests + // const openRequestsResponse = await randclient.getOpenRandomRequests(PROVIDER_ID); + // const openRequestIds = openRequestsResponse.activeRequests.request_ids || []; + // console.log("Open requests:", openRequestIds); - // Track outstanding requests - for (const requestId of openRequestIds) { - if (!outstandingRequests.has(requestId)) { - console.log(`Tracking new request: ${requestId}`); - outstandingRequests.add(requestId); - } - } + // // Track outstanding requests + // for (const requestId of openRequestIds) { + // if (!outstandingRequests.has(requestId)) { + // console.log(`Tracking new request: ${requestId}`); + // outstandingRequests.add(requestId); + // } + // } - // Check the status of outstanding requests - if (outstandingRequests.size > 0) { - const randomRequestsResponse = await randclient.getRandomRequests(Array.from(outstandingRequests)); - const requests = randomRequestsResponse.randomRequestResponses || []; // Adjust based on actual response structure - console.log(randomRequestsResponse) - console.log(requests) + // // Check the status of outstanding requests + // if (outstandingRequests.size > 0) { + // const randomRequestsResponse = await randclient.getRandomRequests(Array.from(outstandingRequests)); + // const requests = randomRequestsResponse.randomRequestResponses || []; // Adjust based on actual response structure + // console.log(randomRequestsResponse) + // console.log(requests) - // for (const request of requests) { - // const requestId = request.requestId; // Adjust if property has a different name - // if (request?.status === "fulfilled") { - // const fulfilledTime = Date.now(); - // const timeToFulfill = fulfilledTime - request.createdTime; // Adjust if createdTime exists - // totalTimeToFulfill += timeToFulfill; - // fulfilledRequests++; - // console.log(`Request ${requestId} fulfilled. Time to fulfill: ${timeToFulfill}ms`); - // outstandingRequests.delete(requestId); // Stop tracking fulfilled requests - // } else { - // console.log(`Request ${requestId} is still being processed.`); - // } - // } - } + // // for (const request of requests) { + // // const requestId = request.requestId; // Adjust if property has a different name + // // if (request?.status === "fulfilled") { + // // const fulfilledTime = Date.now(); + // // const timeToFulfill = fulfilledTime - request.createdTime; // Adjust if createdTime exists + // // totalTimeToFulfill += timeToFulfill; + // // fulfilledRequests++; + // // console.log(`Request ${requestId} fulfilled. Time to fulfill: ${timeToFulfill}ms`); + // // outstandingRequests.delete(requestId); // Stop tracking fulfilled requests + // // } else { + // // console.log(`Request ${requestId} is still being processed.`); + // // } + // // } + // } - // Calculate and log stats - if (fulfilledRequests > 0) { - const avgTimeToFulfill = totalTimeToFulfill / fulfilledRequests; + // // Calculate and log stats + // if (fulfilledRequests > 0) { + // const avgTimeToFulfill = totalTimeToFulfill / fulfilledRequests; - console.log(` - Total Random Called: ${totalRandomCalled} - Outstanding Requests: ${outstandingRequests.size} - Average Time to Fulfill: ${avgTimeToFulfill}ms - `); - } + // console.log(` + // Total Random Called: ${totalRandomCalled} + // Outstanding Requests: ${outstandingRequests.size} + // Average Time to Fulfill: ${avgTimeToFulfill}ms + // `); + // } // Wait before next cycle await delay(RETRY_DELAY_MS); diff --git a/verifiable-delay-function/src/protocol_constants.py b/verifiable-delay-function/src/protocol_constants.py index 4cc3e00..ba1788b 100644 --- a/verifiable-delay-function/src/protocol_constants.py +++ b/verifiable-delay-function/src/protocol_constants.py @@ -1,5 +1,5 @@ # protocol_constants.py BIT_SIZE = 2048 # RSA modulus bit size -TOTAL_SQUARINGS = 10000000 # T - Total squarings for delay +TOTAL_SQUARINGS = 3000000 # T - Total squarings for delay NUM_SEGMENTS = 10 # Number of segments for parallel verification From 0f684dd3618fbadd05dfbccf46f9a5c37dbeff4a Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 19 Dec 2024 14:09:34 -0500 Subject: [PATCH 08/51] added new features --- docker-compose.yml | 22 +++++- orchestrator/package.json | 2 +- orchestrator/src/app.ts | 77 +++++++++++++------ requester/package.json | 2 +- .../src/protocol_constants.py | 4 +- 5 files changed, 76 insertions(+), 31 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4063202..8a2df54 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.1.50 + image: randao/orchestrator:v0.1.63 depends_on: postgres: condition: service_healthy @@ -42,7 +42,7 @@ services: - ./wallet.json:/app/wallet.json # Mount wallet.json into the container # requester: - # image: randao/requester:v0.1.6 + # image: randao/requester:v0.1.11 # environment: # PATH_TO_WALLET: /app/wallet.json # Path inside the container # REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} @@ -53,6 +53,22 @@ services: # volumes: # - ./wallet.json:/app/wallet.json # Mount wallet.json into the container + dbeaver: + image: dbeaver/cloudbeaver:23.2.0 + environment: + CB_SERVER_SERVER_PORT: 8080 + CB_SERVER_ADMIN_NAME: "admin" + CB_SERVER_ADMIN_PASSWORD: "admin123" + depends_on: + postgres: + condition: service_healthy + networks: + - backend + ports: + - "8081:8978" # Expose the DBeaver web UI + volumes: + - dbeaver-data:/opt/cloudbeaver/workspace + networks: backend: name: backend # This will set the network name explicitly @@ -61,3 +77,5 @@ networks: volumes: pgdata: driver: local + dbeaver-data: + driver: local diff --git a/orchestrator/package.json b/orchestrator/package.json index 67bb916..d1ef1d0 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "^3", + "ao-process-clients":"3.4.2", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 31fd2ea..623bbf9 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -25,18 +25,17 @@ const dbConfig = { // Constants for configuration const POLLING_INTERVAL_MS = 5000; -const MINIMUM_ENTRIES = 50; -const TARGET_ENTRIES = 75; -const DROP_CHANCE = 0.005; +const MINIMUM_ENTRIES = 250; +const TARGET_ENTRIES = 500; //Expected increments per second=10×0.005=0.05 //180 times per hour //4,320 times per day //1,576,800 times per year -const MAX_OUTSTANDING_REQUESTS = 10; -const MAX_OUTSTANDING_FULFILLMENTS = 10; +const MAX_OUTSTANDING_REQUESTS = 50; +const MAX_OUTSTANDING_FULFILLMENTS = 50; const MAX_RETRIES = 10; const RETRY_DELAY_MS = 10000; -const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.1'; +const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.2'; const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks @@ -209,18 +208,39 @@ async function monitorDockerContainers(): Promise { const container = docker.getContainer(containerId); const containerInfo = await container.inspect(); + // Check if the container is already stopped (exited) if (containerInfo.State.Status === 'exited') { console.log(`Docker container stopped: ${containerId}`); - await container.remove(); - ongoingContainers.delete(containerId); + + // Attempt to remove the container, handling possible errors gracefully + try { + await container.remove({ force: true }); // Force removal to avoid "in progress" errors + console.log(`Docker container removed: ${containerId}`); + ongoingContainers.delete(containerId); + } catch (removeError) { + if (isDockerError(removeError) && removeError.statusCode === 409) { + // Error 409 means removal is in progress, so skip this container for now + console.log(`Removal of container ${containerId} is already in progress. Skipping.`); + } else { + // Handle other errors that might occur during container removal + console.error(`Error removing Docker container ${containerId}:`, removeError); + } + } } } catch (error) { - console.error(`Error monitoring Docker container ${containerId}:`, error); + console.error(`Error inspecting Docker container ${containerId}:`, error); ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) } } } +// Helper function to type guard Docker errors +function isDockerError(error: unknown): error is { statusCode: number } { + return typeof error === 'object' && error !== null && 'statusCode' in error && typeof (error as any).statusCode === 'number'; +} + + + // Function to check if there are fewer than MINIMUM_ENTRIES and fetch until TARGET_ENTRIES async function checkAndFetchIfNeeded(client: Client): Promise { try { @@ -321,7 +341,7 @@ async function fulfillRandomOutput(client: Client, requestId: string): Promise { - console.log("Starting Polling...") + console.log("Starting Polling..."); await checkAndFetchIfNeeded(client); try { @@ -342,16 +362,15 @@ async function polling(client: Client): Promise { // Part 1: Fetch open requests const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); console.log("Open requests fetched:", openRequests); - if (false) { - clearAllOutputRequests(client) - } - else { - + if (false) { + clearAllOutputRequests(client); + } else { // Part 2: Fulfill Challenge requests if (openRequests && openRequests.activeChallengeRequests) { for (const requestId of openRequests.activeChallengeRequests.request_ids) { + console.log("Max outstanding is "+ MAX_OUTSTANDING_FULFILLMENTS) if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) break; try { // Fetch modulus and input from the database @@ -362,16 +381,15 @@ async function polling(client: Client): Promise { } const { id: dbId, modulus, input } = res.rows[0]; - // console.log(`Fetched entry from database - ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); console.log(`Fetched entry from database - ID: ${dbId}, RequestID: ${requestId}`); // Update the database to save the requestId with the entry await client.query('UPDATE verifiable_delay_functions SET request_id = $1 WHERE id = $2', [requestId, dbId]); console.log(`Updated database entry with request ID: ${requestId} for entry ID: ${dbId}`); ongoingFulfillments.add(dbId); console.log(`Processing challenge request ID: ${requestId}`); - fulfillRandomChallenge(client, dbId, requestId, modulus, input); + await fulfillRandomChallenge(client, dbId, requestId, modulus, input); } catch (error) { - console.log(error) + console.log(error); } } } else { @@ -388,7 +406,9 @@ async function polling(client: Client): Promise { console.log('No Output requests'); } + // Part 4: Check for completed fulfillments that are no longer in the challenge or output list + const noLongerUsedIds: string[] = []; for (const ongoingId of ongoingFulfillments) { const challengeInProgress = openRequests.activeChallengeRequests?.request_ids.includes(ongoingId); const outputInProgress = openRequests.activeOutputRequests?.request_ids.includes(ongoingId); @@ -398,12 +418,18 @@ async function polling(client: Client): Promise { console.log(` - In active outputs: ${outputInProgress}`); if (!challengeInProgress && !outputInProgress) { - console.log(`Entry with ID: ${ongoingId} is no longer requested.`); - // await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [ongoingId]); - // console.log(`Entry with ID: ${ongoingId} deleted from database.`); - // ongoingFulfillments.delete(ongoingId); - // totalProvided++; - // console.log("total provided = " + totalProvided); + noLongerUsedIds.push(ongoingId); + } + } + + // Log all IDs that are no longer in use in a single line + if (noLongerUsedIds.length > 0) { + console.log(`No longer in use: ${noLongerUsedIds.join(', ')}`); + // Optionally remove them from the ongoing fulfillments list if necessary + for (const id of noLongerUsedIds) { + console.log("Removing id from list:" + id) + //await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [id]); + ongoingFulfillments.delete(id); } } } @@ -414,6 +440,7 @@ async function polling(client: Client): Promise { + // Main function async function run(): Promise { const client = await connectWithRetry(); diff --git a/requester/package.json b/requester/package.json index 68a050e..e502c8a 100644 --- a/requester/package.json +++ b/requester/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "^3", + "ao-process-clients": "3.4.2", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/verifiable-delay-function/src/protocol_constants.py b/verifiable-delay-function/src/protocol_constants.py index ba1788b..1157d0c 100644 --- a/verifiable-delay-function/src/protocol_constants.py +++ b/verifiable-delay-function/src/protocol_constants.py @@ -1,5 +1,5 @@ # protocol_constants.py -BIT_SIZE = 2048 # RSA modulus bit size -TOTAL_SQUARINGS = 3000000 # T - Total squarings for delay +BIT_SIZE = 1024 # RSA modulus bit size +TOTAL_SQUARINGS = 10000 # T - Total squarings for delay NUM_SEGMENTS = 10 # Number of segments for parallel verification From 75bbbdd7575b88a34a3ae204150457f1e5dc0325 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 20 Dec 2024 23:54:50 -0500 Subject: [PATCH 09/51] V1 working --- docker-compose.yml | 24 +- orchestrator/src/app.ts | 962 +++++++++--------- requester/src/app.ts | 2 +- .../src/protocol_constants.py | 4 +- 4 files changed, 512 insertions(+), 480 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8a2df54..40d406d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.1.63 + image: randao/orchestrator:v0.1.78 depends_on: postgres: condition: service_healthy @@ -41,17 +41,17 @@ services: - /var/run/docker.sock:/var/run/docker.sock # Mount Docker socket - ./wallet.json:/app/wallet.json # Mount wallet.json into the container - # requester: - # image: randao/requester:v0.1.11 - # environment: - # PATH_TO_WALLET: /app/wallet.json # Path inside the container - # REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} - # PROVIDER_ID: ${PROVIDER_ID} - # DOCKER_NETWORK: backend # Passing the network name - # networks: - # - backend - # volumes: - # - ./wallet.json:/app/wallet.json # Mount wallet.json into the container + requester: + image: randao/requester:v0.1.13 + environment: + PATH_TO_WALLET: /app/wallet.json # Path inside the container + REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} + PROVIDER_ID: ${PROVIDER_ID} + DOCKER_NETWORK: backend # Passing the network name + networks: + - backend + volumes: + - ./wallet.json:/app/wallet.json # Mount wallet.json into the container dbeaver: image: dbeaver/cloudbeaver:23.2.0 diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 623bbf9..ea542dd 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -1,465 +1,497 @@ -import { Client } from 'pg'; -import Docker from 'dockerode'; -import AWS from 'aws-sdk'; -import { getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomClientConfig } from "ao-process-clients" - -// const RANDOM_CONFIG: RandomClientConfig = { -// tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, -// processId: getRandomClientAutoConfiguration().processId, -// wallet: JSON.parse(process.env.WALLET_JSON!), -// environment: 'mainnet' -// } -const randclient: IRandomClient = RandomClient.autoConfiguration() -//const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG) - -const docker = new Docker(); - -// Database configuration -const dbConfig = { - host: process.env.DB_HOST || 'localhost', - port: parseInt(process.env.DB_PORT || '5432', 10), - user: process.env.DB_USER || 'myuser', - password: process.env.DB_PASSWORD || 'mypassword', - database: process.env.DB_NAME || 'mydatabase', -}; - -// Constants for configuration -const POLLING_INTERVAL_MS = 5000; -const MINIMUM_ENTRIES = 250; -const TARGET_ENTRIES = 500; -//Expected increments per second=10×0.005=0.05 -//180 times per hour -//4,320 times per day -//1,576,800 times per year -const MAX_OUTSTANDING_REQUESTS = 50; -const MAX_OUTSTANDING_FULFILLMENTS = 50; -const MAX_RETRIES = 10; -const RETRY_DELAY_MS = 10000; -const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.2'; -const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; -const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); -const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks -const ongoingContainers = new Set(); // Track container IDs of running Docker containers -const ongoingFulfillments = new Set(); // Track request IDs for ongoing fulfillments -const PROVIDER_ID = process.env.PROVIDER_ID || "0"; -let ongoingRequest = false; -let spotInterruptions = 0; -let totalProvided = 0; -// Retry logic for connecting to PostgreSQL -async function connectWithRetry(): Promise { - const client = new Client(dbConfig); - for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { - try { - await client.connect(); - console.log(`Connected to PostgreSQL database (Attempt ${attempt})`); - return client; - } catch (error) { - console.error(`Connection attempt ${attempt} failed, retrying in ${RETRY_DELAY_MS / 1000} seconds...`); - await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS)); - } - } - throw new Error("Failed to connect to PostgreSQL after multiple attempts"); -} - -// Setup the `verifiable_delay_functions` table if not exists -async function setupDatabase(client: Client): Promise { - - // // Drop the table if it exists - // await client.query(` - // DROP TABLE IF EXISTS verifiable_delay_functions; - // `); - // console.log("'verifiable_delay_functions' table dropped."); - - await client.query(` - CREATE TABLE IF NOT EXISTS verifiable_delay_functions ( - id TEXT PRIMARY KEY, -- Define as TEXT to match UUID format - request_id TEXT, - modulus TEXT NOT NULL, - input TEXT NOT NULL, - output TEXT NOT NULL, - proof JSON NOT NULL, - date TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - `); - console.log("Database setup complete. 'verifiable_delay_functions' table is ready."); -} - - -// Modified function to trigger VDF job pod using ECS or Docker -async function triggerVDFJobPod(): Promise { - if (ENVIRONMENT === 'cloud') { - try { - console.log("Cloud environment detected. Launching ECS task."); - const result = await ecs.runTask({ - cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', - taskDefinition: 'vdf-job', - capacityProviderStrategy: [ - { - capacityProvider: 'FARGATE_SPOT', - weight: 1 - } - ], - networkConfiguration: { - awsvpcConfiguration: { - subnets: [process.env.SUBNET_ID || 'subnet-12345678'], - securityGroups: [process.env.SECURITY_GROUP || 'sg-12345678'], - assignPublicIp: 'ENABLED' - } - }, - overrides: { - containerOverrides: [ - { - name: 'vdf_job_container', - environment: [ - { name: 'DATABASE_TYPE', value: 'postgresql' }, - { name: 'DATABASE_HOST', value: process.env.DB_HOST || 'cloud-postgres-host' }, - { name: 'DATABASE_PORT', value: process.env.DB_PORT || '5432' }, - { name: 'DATABASE_USER', value: process.env.DB_USER || 'myuser' }, - { name: 'DATABASE_PASSWORD', value: process.env.DB_PASSWORD || 'mypassword' }, - { name: 'DATABASE_NAME', value: process.env.DB_NAME || 'mydatabase' }, - ] - } - ] - }, - count: 1 - }).promise(); - - const taskArn = result.tasks?.[0]?.taskArn; - if (taskArn) { - ongoingTasks.add(taskArn); - console.log(`ECS task started successfully: ${taskArn}`); - return taskArn; - } - return null; - } catch (error: any) { - if (error?.code === 'SpotCapacityNotAvailableException') { - spotInterruptions++; - console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); - } else { - console.error("Error launching ECS task:", error); - } - return null; - } - } else { - const containerName = `vdf_job_${Date.now()}`; - console.log(`Starting Docker container with name: ${containerName}`); - const container = await docker.createContainer({ - Image: VDF_JOB_IMAGE, - Cmd: ['python', 'main.py'], - Env: [ - `DATABASE_TYPE=postgresql`, - `DATABASE_HOST=${dbConfig.host}`, - `DATABASE_PORT=${dbConfig.port.toString()}`, - `DATABASE_USER=${dbConfig.user}`, - `DATABASE_PASSWORD=${dbConfig.password}`, - `DATABASE_NAME=${dbConfig.database}`, - ], - HostConfig: { - NetworkMode: 'backend', - }, - name: containerName - }); - - await container.start(); - ongoingContainers.add(container.id); - console.log(`Docker container ${containerName} started successfully.`); - return container.id; - } -} - -// Modified function to wait for ECS tasks to complete and remove them from tracking -async function monitorECSTasks(): Promise { - if (ongoingTasks.size === 0) return; - - const describeTasksResult = await ecs.describeTasks({ - cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', - tasks: Array.from(ongoingTasks) - }).promise(); - - describeTasksResult.tasks?.forEach(task => { - // Check capacity provider name to determine if it is running Fargate or Fargate Spot - const capacityProvider = task.capacityProviderName || 'Unknown'; - - console.log(`ECS task: ${task.taskArn}, Capacity Provider: ${capacityProvider}`); - - if (task.lastStatus === 'STOPPED') { - console.log(`ECS task stopped: ${task.taskArn}`); - if (task.stoppedReason) { - console.log(`Task stopped reason: ${task.stoppedReason}`); - if (task.stoppedReason.includes('Host EC2 instance termination')) { - spotInterruptions++; - console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); - } - } else { - console.log('Task stopped due to normal completion.'); - } - ongoingTasks.delete(task.taskArn as string); - } - }); -} - - -// Function to wait for Docker containers to complete and remove them from tracking -async function monitorDockerContainers(): Promise { - if (ongoingContainers.size === 0) return; - - for (const containerId of ongoingContainers) { - try { - const container = docker.getContainer(containerId); - const containerInfo = await container.inspect(); - - // Check if the container is already stopped (exited) - if (containerInfo.State.Status === 'exited') { - console.log(`Docker container stopped: ${containerId}`); - - // Attempt to remove the container, handling possible errors gracefully - try { - await container.remove({ force: true }); // Force removal to avoid "in progress" errors - console.log(`Docker container removed: ${containerId}`); - ongoingContainers.delete(containerId); - } catch (removeError) { - if (isDockerError(removeError) && removeError.statusCode === 409) { - // Error 409 means removal is in progress, so skip this container for now - console.log(`Removal of container ${containerId} is already in progress. Skipping.`); - } else { - // Handle other errors that might occur during container removal - console.error(`Error removing Docker container ${containerId}:`, removeError); - } - } - } - } catch (error) { - console.error(`Error inspecting Docker container ${containerId}:`, error); - ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) - } - } -} - -// Helper function to type guard Docker errors -function isDockerError(error: unknown): error is { statusCode: number } { - return typeof error === 'object' && error !== null && 'statusCode' in error && typeof (error as any).statusCode === 'number'; -} - - - -// Function to check if there are fewer than MINIMUM_ENTRIES and fetch until TARGET_ENTRIES -async function checkAndFetchIfNeeded(client: Client): Promise { - try { - if (ongoingRequest) return; - const res = await client.query('SELECT COUNT(*) AS count FROM verifiable_delay_functions WHERE request_id IS NULL'); - const currentCount = parseInt(res.rows[0].count, 10); // Parse the count as an integer - console.log("Total usable db entries: " + currentCount); - - const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(currentCount); - console.log("Updates onchain: " + updateAvailableValuesResult) - console.log(await randclient.getProviderAvailableValues(PROVIDER_ID)) - if (currentCount < MINIMUM_ENTRIES) { - const entriesNeeded = TARGET_ENTRIES - currentCount; - console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries to reach ${TARGET_ENTRIES}...`); - ongoingRequest = true; - let tasksTriggered = 0; - - while (tasksTriggered < entriesNeeded) { - if (ENVIRONMENT === 'cloud' && ongoingTasks.size >= MAX_OUTSTANDING_REQUESTS) { - await monitorECSTasks(); - } else if (ENVIRONMENT !== 'cloud' && ongoingContainers.size >= MAX_OUTSTANDING_REQUESTS) { - await monitorDockerContainers(); - } else { - const taskArn = await triggerVDFJobPod(); - if (taskArn) { - tasksTriggered++; - console.log(`Task triggered: ${taskArn}. Total ongoing tasks: ${ongoingTasks.size}. Total ongoing containers ${+ ongoingContainers.size}`); - } - } - } - ongoingRequest = false; - } - } catch (error) { - console.error('Error during check and fetch:', error); - ongoingRequest = false; - } -} - -// Function to clear all output requests -async function clearAllOutputRequests(client: Client): Promise { - try { - // Fetch the current open output requests - const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); - console.log("Open requests fetched:", openRequests); - - if (openRequests && openRequests.activeOutputRequests) { - openRequests.activeOutputRequests.request_ids.forEach((requestId) => { - console.log(`Sending "No data" for output request ID: ${requestId}`); - - // Send "No data" as output for each request (do not await, fire and forget) - randclient.postVDFOutputAndProof(requestId, "No data", "No data").then(() => { - console.log(`"No data" sent for request ID: ${requestId}`); - }).catch((error) => { - console.error(`Error sending "No data" for request ID: ${requestId}:`, error); - }); - }); - } else { - console.log('No Output requests to clear'); - } - } catch (error) { - console.error('An error occurred while clearing output requests:', error); - } -} - - -// Function to post VDF challenge -async function fulfillRandomChallenge(client: Client, dbId: string, requestId: string, modulus: string, input: string): Promise { - if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) return; - - try { - // Assume dbId, requestId, modulus, and input are passed in as parameters - - console.log(`Received entry details - ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); - - - console.log(`Posting VDF challenge for entry ID: ${dbId}, request ID: ${requestId}`); - await randclient.postVDFChallenge(requestId, modulus, input); - console.log(`Challenge posted for request ID: ${requestId}. Waiting to post proof...`); - } catch (error) { - console.error(`Error posting VDF challenge for request ID: ${requestId}:`, error); - } -} - - -// Function to post VDF output and proof -async function fulfillRandomOutput(client: Client, requestId: string): Promise { - try { - // Fetch the output and proof from the database using the requestId - const res = await client.query('SELECT id, output, proof FROM verifiable_delay_functions WHERE request_id = $1', [requestId]); - if (!res.rowCount) { - console.error(`No entry found for request ID: ${requestId}`); - return; - } - const { id: dbId, output, proof } = res.rows[0]; - - // console.log(`Fetched entry from database for output - ID: ${dbId}, Output: ${output}, Proof: ${proof}`); - console.log(`Fetched entry from database for output - ID: ${dbId}, requestID: ${requestId}`); - // Stringify the proof if it is an array - const proofString = Array.isArray(proof) ? JSON.stringify(proof) : proof; - - console.log(`Posting VDF output and proof for - ID: ${dbId}, request ID: ${requestId}`); - await randclient.postVDFOutputAndProof(requestId, output, proofString); - console.log(`Proof posted for request ID: ${requestId}`); - - ongoingFulfillments.delete(dbId); - } catch (error) { - console.error(`Error fulfilling random output for request ID: ${requestId}:`, error); - } -} - -// Modified polling function to fulfill open requests if they exist -async function polling(client: Client): Promise { - console.log("Starting Polling..."); - await checkAndFetchIfNeeded(client); - - try { - console.log(PROVIDER_ID); - - // Part 1: Fetch open requests - const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); - console.log("Open requests fetched:", openRequests); - - if (false) { - clearAllOutputRequests(client); - } else { - - // Part 2: Fulfill Challenge requests - if (openRequests && openRequests.activeChallengeRequests) { - for (const requestId of openRequests.activeChallengeRequests.request_ids) { - console.log("Max outstanding is "+ MAX_OUTSTANDING_FULFILLMENTS) - if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) break; - try { - // Fetch modulus and input from the database - const res = await client.query('SELECT * FROM verifiable_delay_functions WHERE request_id IS NULL ORDER BY id ASC LIMIT 1'); - if (!res.rowCount) { - console.error(`No available entry found in the database.`); - return; - } - const { id: dbId, modulus, input } = res.rows[0]; - - console.log(`Fetched entry from database - ID: ${dbId}, RequestID: ${requestId}`); - // Update the database to save the requestId with the entry - await client.query('UPDATE verifiable_delay_functions SET request_id = $1 WHERE id = $2', [requestId, dbId]); - console.log(`Updated database entry with request ID: ${requestId} for entry ID: ${dbId}`); - ongoingFulfillments.add(dbId); - console.log(`Processing challenge request ID: ${requestId}`); - await fulfillRandomChallenge(client, dbId, requestId, modulus, input); - } catch (error) { - console.log(error); - } - } - } else { - console.log('No Challenge requests'); - } - - // Part 3: Fulfill Output requests - if (openRequests && openRequests.activeOutputRequests) { - for (const requestId of openRequests.activeOutputRequests.request_ids) { - console.log(`Processing output request ID: ${requestId}`); - fulfillRandomOutput(client, requestId); - } - } else { - console.log('No Output requests'); - } - - - // Part 4: Check for completed fulfillments that are no longer in the challenge or output list - const noLongerUsedIds: string[] = []; - for (const ongoingId of ongoingFulfillments) { - const challengeInProgress = openRequests.activeChallengeRequests?.request_ids.includes(ongoingId); - const outputInProgress = openRequests.activeOutputRequests?.request_ids.includes(ongoingId); - - console.log(`Checking ID: ${ongoingId}`); - console.log(` - In active challenges: ${challengeInProgress}`); - console.log(` - In active outputs: ${outputInProgress}`); - - if (!challengeInProgress && !outputInProgress) { - noLongerUsedIds.push(ongoingId); - } - } - - // Log all IDs that are no longer in use in a single line - if (noLongerUsedIds.length > 0) { - console.log(`No longer in use: ${noLongerUsedIds.join(', ')}`); - // Optionally remove them from the ongoing fulfillments list if necessary - for (const id of noLongerUsedIds) { - console.log("Removing id from list:" + id) - //await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [id]); - ongoingFulfillments.delete(id); - } - } - } - } catch (error) { - console.error('An error occurred while fetching open random requests:', error); - } -} - - - - -// Main function -async function run(): Promise { - const client = await connectWithRetry(); - await setupDatabase(client); - - setInterval(async () => { - const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); - console.log(`Periodic log - Current database size: ${res.rows[0].count}`); - }, 10000); - - setInterval(async () => { - await polling(client); - }, POLLING_INTERVAL_MS); - - process.on("SIGTERM", async () => { - console.log("SIGTERM received. Closing database connection."); - await client.end(); - process.exit(0); - }); -} - -run().catch((err) => console.error(`Error in main function: ${err}`)); +import { Client } from 'pg'; +import Docker from 'dockerode'; +import AWS from 'aws-sdk'; +import { getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomClientConfig } from "ao-process-clients" + +const RANDOM_CONFIG: RandomClientConfig = { + tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, + processId: "vgH7EXVs6-vxxilja6lkBruHlgOkyqddFVg-BVp3eJc", + wallet: JSON.parse(process.env.WALLET_JSON!), + environment: 'mainnet' +} +//const randclient: IRandomClient = RandomClient.autoConfiguration() +const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG) + +const docker = new Docker(); + +// Database configuration +const dbConfig = { + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT || '5432', 10), + user: process.env.DB_USER || 'myuser', + password: process.env.DB_PASSWORD || 'mypassword', + database: process.env.DB_NAME || 'mydatabase', +}; + +// Constants for configuration +const POLLING_INTERVAL_MS = 5000; +const MINIMUM_ENTRIES = 250; +const TARGET_ENTRIES = 500; +//Expected increments per second=10×0.005=0.05 +//180 times per hour +//4,320 times per day +//1,576,800 times per year +const MAX_OUTSTANDING_REQUESTS = 50; +const MAX_OUTSTANDING_FULFILLMENTS = 50; +const MAX_RETRIES = 10; +const RETRY_DELAY_MS = 10000; +const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.4'; +const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; +const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); +const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks +const ongoingContainers = new Set(); // Track container IDs of running Docker containers +const ongoingFulfillments = new Set(); // Track request IDs for ongoing fulfillments +const PROVIDER_ID = process.env.PROVIDER_ID || "0"; +let ongoingRequest = false; +let spotInterruptions = 0; +let totalProvided = 0; +// Retry logic for connecting to PostgreSQL +async function connectWithRetry(): Promise { + const client = new Client(dbConfig); + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + await client.connect(); + console.log(`Connected to PostgreSQL database (Attempt ${attempt})`); + return client; + } catch (error) { + console.error(`Connection attempt ${attempt} failed, retrying in ${RETRY_DELAY_MS / 1000} seconds...`); + await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS)); + } + } + throw new Error("Failed to connect to PostgreSQL after multiple attempts"); +} + +// Setup the `verifiable_delay_functions` table if not exists +async function setupDatabase(client: Client): Promise { + + // // Drop the table if it exists + // await client.query(` + // DROP TABLE IF EXISTS verifiable_delay_functions; + // `); + // console.log("'verifiable_delay_functions' table dropped."); + + await client.query(` + CREATE TABLE IF NOT EXISTS verifiable_delay_functions ( + id TEXT PRIMARY KEY, -- Define as TEXT to match UUID format + request_id TEXT, + modulus TEXT NOT NULL, + input TEXT NOT NULL, + output TEXT NOT NULL, + proof JSON NOT NULL, + date TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + console.log("Database setup complete. 'verifiable_delay_functions' table is ready."); +} + +// Modified function to trigger VDF job pod using ECS or Docker +async function triggerVDFJobPod(): Promise { + if (ENVIRONMENT === 'cloud') { + try { + console.log("Cloud environment detected. Launching ECS task."); + const result = await ecs.runTask({ + cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', + taskDefinition: 'vdf-job', + capacityProviderStrategy: [ + { + capacityProvider: 'FARGATE_SPOT', + weight: 1 + } + ], + networkConfiguration: { + awsvpcConfiguration: { + subnets: [process.env.SUBNET_ID || 'subnet-12345678'], + securityGroups: [process.env.SECURITY_GROUP || 'sg-12345678'], + assignPublicIp: 'ENABLED' + } + }, + overrides: { + containerOverrides: [ + { + name: 'vdf_job_container', + environment: [ + { name: 'DATABASE_TYPE', value: 'postgresql' }, + { name: 'DATABASE_HOST', value: process.env.DB_HOST || 'cloud-postgres-host' }, + { name: 'DATABASE_PORT', value: process.env.DB_PORT || '5432' }, + { name: 'DATABASE_USER', value: process.env.DB_USER || 'myuser' }, + { name: 'DATABASE_PASSWORD', value: process.env.DB_PASSWORD || 'mypassword' }, + { name: 'DATABASE_NAME', value: process.env.DB_NAME || 'mydatabase' }, + ] + } + ] + }, + count: 1 + }).promise(); + + const taskArn = result.tasks?.[0]?.taskArn; + if (taskArn) { + ongoingTasks.add(taskArn); + console.log(`ECS task started successfully: ${taskArn}`); + return taskArn; + } + return null; + } catch (error: any) { + if (error?.code === 'SpotCapacityNotAvailableException') { + spotInterruptions++; + console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); + } else { + console.error("Error launching ECS task:", error); + } + return null; + } + } else { + const containerName = `vdf_job_${Date.now()}`; + console.log(`Starting Docker container with name: ${containerName}`); + const container = await docker.createContainer({ + Image: VDF_JOB_IMAGE, + Cmd: ['python', 'main.py'], + Env: [ + `DATABASE_TYPE=postgresql`, + `DATABASE_HOST=${dbConfig.host}`, + `DATABASE_PORT=${dbConfig.port.toString()}`, + `DATABASE_USER=${dbConfig.user}`, + `DATABASE_PASSWORD=${dbConfig.password}`, + `DATABASE_NAME=${dbConfig.database}`, + ], + HostConfig: { + NetworkMode: 'backend', + }, + name: containerName + }); + + await container.start(); + ongoingContainers.add(container.id); + console.log(`Docker container ${containerName} started successfully.`); + return container.id; + } +} + +// Modified function to wait for ECS tasks to complete and remove them from tracking +async function monitorECSTasks(): Promise { + if (ongoingTasks.size === 0) return; + + const describeTasksResult = await ecs.describeTasks({ + cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', + tasks: Array.from(ongoingTasks) + }).promise(); + + describeTasksResult.tasks?.forEach(task => { + // Check capacity provider name to determine if it is running Fargate or Fargate Spot + const capacityProvider = task.capacityProviderName || 'Unknown'; + + console.log(`ECS task: ${task.taskArn}, Capacity Provider: ${capacityProvider}`); + + if (task.lastStatus === 'STOPPED') { + console.log(`ECS task stopped: ${task.taskArn}`); + if (task.stoppedReason) { + console.log(`Task stopped reason: ${task.stoppedReason}`); + if (task.stoppedReason.includes('Host EC2 instance termination')) { + spotInterruptions++; + console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); + } + } else { + console.log('Task stopped due to normal completion.'); + } + ongoingTasks.delete(task.taskArn as string); + } + }); +} + +// Function to wait for Docker containers to complete and remove them from tracking +async function monitorDockerContainers(): Promise { + if (ongoingContainers.size === 0) return; + + for (const containerId of ongoingContainers) { + try { + const container = docker.getContainer(containerId); + const containerInfo = await container.inspect(); + + // Check if the container is already stopped (exited) + if (containerInfo.State.Status === 'exited') { + console.log(`Docker container stopped: ${containerId}`); + + // Attempt to remove the container, handling possible errors gracefully + try { + await container.remove({ force: true }); // Force removal to avoid "in progress" errors + console.log(`Docker container removed: ${containerId}`); + ongoingContainers.delete(containerId); + } catch (removeError) { + if (isDockerError(removeError) && removeError.statusCode === 409) { + // Error 409 means removal is in progress, so skip this container for now + console.log(`Removal of container ${containerId} is already in progress. Skipping.`); + } else { + // Handle other errors that might occur during container removal + console.error(`Error removing Docker container ${containerId}:`, removeError); + } + } + } + } catch (error) { + console.error(`Error inspecting Docker container ${containerId}:`, error); + ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) + } + } +} + +// Helper function to type guard Docker errors +function isDockerError(error: unknown): error is { statusCode: number } { + return typeof error === 'object' && error !== null && 'statusCode' in error && typeof (error as any).statusCode === 'number'; +} + +// Function to process hex output for 64-bit modulus +function hexMod64Bit(expectedOutput: string): { expectedOutput64BitBase10: string } { + // Parse the hexadecimal string into a BigInt + const number = BigInt(`0x${expectedOutput}`); + + // Define the 64-bit modulus (2^64 - 1) + const modulus = BigInt("0xFFFFFFFFFFFFFFFF"); + + // Keep dividing by modulus until we get a remainder less than modulus + let remainder = number; + while (remainder >= modulus) { + remainder = remainder % modulus; + } + + // Return the remainder in base 10 + return { + expectedOutput64BitBase10: remainder.toString(), + }; +} + +// Function to prepend 0x to hex strings +function addHexPrefix(value: string): string { + return value.startsWith('0x') ? value : `0x${value}`; +} + +// Function to check if there are fewer than MINIMUM_ENTRIES and fetch until TARGET_ENTRIES +async function checkAndFetchIfNeeded(client: Client): Promise { + try { + if (ongoingRequest) return; + const res = await client.query('SELECT COUNT(*) AS count FROM verifiable_delay_functions WHERE request_id IS NULL'); + const currentCount = parseInt(res.rows[0].count, 10); // Parse the count as an integer + console.log("Total usable db entries: " + currentCount); + + // Only try to update available values if we have some entries + if (currentCount > 0) { + try { + const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(currentCount); + console.log("Updates onchain: " + updateAvailableValuesResult); + console.log(await randclient.getProviderAvailableValues(PROVIDER_ID)); + } catch (error) { + console.log("Warning: Could not update available values:", error); + } + } + + if (currentCount < MINIMUM_ENTRIES) { + const entriesNeeded = TARGET_ENTRIES - currentCount; + console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries to reach ${TARGET_ENTRIES}...`); + ongoingRequest = true; + let tasksTriggered = 0; + + while (tasksTriggered < entriesNeeded) { + if (ENVIRONMENT === 'cloud' && ongoingTasks.size >= MAX_OUTSTANDING_REQUESTS) { + await monitorECSTasks(); + } else if (ENVIRONMENT !== 'cloud' && ongoingContainers.size >= MAX_OUTSTANDING_REQUESTS) { + await monitorDockerContainers(); + } else { + const taskArn = await triggerVDFJobPod(); + if (taskArn) { + tasksTriggered++; + console.log(`Task triggered: ${taskArn}. Total ongoing tasks: ${ongoingTasks.size}. Total ongoing containers ${+ongoingContainers.size}`); + } + } + } + ongoingRequest = false; + } + } catch (error) { + console.error('Error during check and fetch:', error); + ongoingRequest = false; + } +} + +// Function to clear all output requests +async function clearAllOutputRequests(client: Client): Promise { + try { + // Fetch the current open output requests + const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); + console.log("Open requests fetched:", openRequests); + + if (openRequests && openRequests.activeOutputRequests) { + openRequests.activeOutputRequests.request_ids.forEach((requestId) => { + console.log(`Sending "No data" for output request ID: ${requestId}`); + + // Send "No data" as output for each request (do not await, fire and forget) + randclient.postVDFOutputAndProof(requestId, "No data", "No data").then(() => { + console.log(`"No data" sent for request ID: ${requestId}`); + }).catch((error) => { + console.error(`Error sending "No data" for request ID: ${requestId}:`, error); + }); + }); + } else { + console.log('No Output requests to clear'); + } + } catch (error) { + console.error('An error occurred while clearing output requests:', error); + } +} + +// Function to post VDF challenge +async function fulfillRandomChallenge(client: Client, dbId: string, requestId: string, modulus: string, input: string): Promise { + if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) return; + + try { + console.log(`Received entry details - ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); + + // Add hex prefix to modulus and input + const hexModulus = addHexPrefix(modulus); + const hexInput = addHexPrefix(input); + + console.log(`Posting VDF challenge for entry ID: ${dbId}, request ID: ${requestId}`); + await randclient.postVDFChallenge(requestId, hexModulus, hexInput); + console.log(`Challenge posted for request ID: ${requestId}. Waiting to post proof...`); + } catch (error) { + console.error(`Error posting VDF challenge for request ID: ${requestId}:`, error); + } +} + +// Function to post VDF output and proof +async function fulfillRandomOutput(client: Client, requestId: string): Promise { + try { + // Fetch the output and proof from the database using the requestId + const res = await client.query('SELECT id, output, proof FROM verifiable_delay_functions WHERE request_id = $1', [requestId]); + if (!res.rowCount) { + console.error(`No entry found for request ID: ${requestId}`); + return; + } + const { id: dbId, output, proof } = res.rows[0]; + + console.log(`Fetched entry from database for output - ID: ${dbId}, requestID: ${requestId}`); + + // Process the output through hexMod64Bit + const processedOutput = hexMod64Bit(output).expectedOutput64BitBase10; + console.log(`Processed output: ${processedOutput} For request ID: ${requestId}`); + // Process the proof array - add hex prefix to each element + let processedProof = proof; + if (Array.isArray(proof)) { + processedProof = proof.map(element => addHexPrefix(element)); + } + const proofString = JSON.stringify(processedProof); + + console.log(`Posting VDF output and proof for - ID: ${dbId}, request ID: ${requestId}`); + await randclient.postVDFOutputAndProof(requestId, processedOutput, proofString); + console.log(`Proof posted for request ID: ${requestId}`); + + ongoingFulfillments.delete(dbId); + } catch (error) { + console.error(`Error fulfilling random output for request ID: ${requestId}:`, error); + } +} + +// Modified polling function to fulfill open requests if they exist +async function polling(client: Client): Promise { + console.log("Starting Polling..."); + await checkAndFetchIfNeeded(client); + + try { + console.log(PROVIDER_ID); + + // Part 1: Fetch open requests + const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); + console.log("Open requests fetched:", openRequests); + + if (false) { + clearAllOutputRequests(client); + } else { + + // Part 2: Fulfill Challenge requests + if (openRequests && openRequests.activeChallengeRequests) { + for (const requestId of openRequests.activeChallengeRequests.request_ids) { + console.log("Max outstanding is " + MAX_OUTSTANDING_FULFILLMENTS) + if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) break; + try { + // Fetch modulus and input from the database + const res = await client.query('SELECT * FROM verifiable_delay_functions WHERE request_id IS NULL ORDER BY id ASC LIMIT 1'); + if (!res.rowCount) { + console.error(`No available entry found in the database.`); + return; + } + const { id: dbId, modulus, input } = res.rows[0]; + + console.log(`Fetched entry from database - ID: ${dbId}, RequestID: ${requestId}`); + // Update the database to save the requestId with the entry + await client.query('UPDATE verifiable_delay_functions SET request_id = $1 WHERE id = $2', [requestId, dbId]); + console.log(`Updated database entry with request ID: ${requestId} for entry ID: ${dbId}`); + ongoingFulfillments.add(dbId); + console.log(`Processing challenge request ID: ${requestId}`); + await fulfillRandomChallenge(client, dbId, requestId, modulus, input); + } catch (error) { + console.log(error); + } + } + } else { + console.log('No Challenge requests'); + } + + // Part 3: Fulfill Output requests + if (openRequests && openRequests.activeOutputRequests) { + for (const requestId of openRequests.activeOutputRequests.request_ids) { + console.log(`Processing output request ID: ${requestId}`); + fulfillRandomOutput(client, requestId); + } + } else { + console.log('No Output requests'); + } + + + // Part 4: Check for completed fulfillments that are no longer in the challenge or output list + const noLongerUsedIds: string[] = []; + for (const ongoingId of ongoingFulfillments) { + const challengeInProgress = openRequests.activeChallengeRequests?.request_ids.includes(ongoingId); + const outputInProgress = openRequests.activeOutputRequests?.request_ids.includes(ongoingId); + + console.log(`Checking ID: ${ongoingId}`); + console.log(` - In active challenges: ${challengeInProgress}`); + console.log(` - In active outputs: ${outputInProgress}`); + + if (!challengeInProgress && !outputInProgress) { + noLongerUsedIds.push(ongoingId); + } + } + + // Log all IDs that are no longer in use in a single line + if (noLongerUsedIds.length > 0) { + console.log(`No longer in use: ${noLongerUsedIds.join(', ')}`); + // Optionally remove them from the ongoing fulfillments list if necessary + for (const id of noLongerUsedIds) { + console.log("Removing id from list:" + id) + //await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [id]); + ongoingFulfillments.delete(id); + } + } + } + } catch (error) { + console.error('An error occurred while fetching open random requests:', error); + } +} + +// Main function +async function run(): Promise { + const client = await connectWithRetry(); + await setupDatabase(client); + + setInterval(async () => { + const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); + console.log(`Periodic log - Current database size: ${res.rows[0].count}`); + }, 10000); + + setInterval(async () => { + await polling(client); + }, POLLING_INTERVAL_MS); + + process.on("SIGTERM", async () => { + console.log("SIGTERM received. Closing database connection."); + await client.end(); + process.exit(0); + }); +} + +run().catch((err) => console.error(`Error in main function: ${err}`)); diff --git a/requester/src/app.ts b/requester/src/app.ts index 3b71834..3e2f97c 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -11,7 +11,7 @@ const CHANCE_TO_CALL_RANDOM = 1; const RANDOM_CONFIG: RandomClientConfig = { tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, - processId: getRandomClientAutoConfiguration().processId, + processId: "vgH7EXVs6-vxxilja6lkBruHlgOkyqddFVg-BVp3eJc", wallet: JSON.parse(process.env.REQUEST_WALLET_JSON!), environment: "mainnet", }; diff --git a/verifiable-delay-function/src/protocol_constants.py b/verifiable-delay-function/src/protocol_constants.py index 1157d0c..2001cb7 100644 --- a/verifiable-delay-function/src/protocol_constants.py +++ b/verifiable-delay-function/src/protocol_constants.py @@ -1,5 +1,5 @@ # protocol_constants.py -BIT_SIZE = 1024 # RSA modulus bit size -TOTAL_SQUARINGS = 10000 # T - Total squarings for delay +BIT_SIZE = 256 # RSA modulus bit size +TOTAL_SQUARINGS = 5000 # T - Total squarings for delay NUM_SEGMENTS = 10 # Number of segments for parallel verification From 4d6ea46df7ac77e98265cf2a7ad389f2c37b00cb Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 21 Dec 2024 13:25:01 -0500 Subject: [PATCH 10/51] added --- requester/docker | 0 requester/src/app.ts | 216 ++++++++++++++++++++++++------------------- 2 files changed, 119 insertions(+), 97 deletions(-) create mode 100644 requester/docker diff --git a/requester/docker b/requester/docker new file mode 100644 index 0000000..e69de29 diff --git a/requester/src/app.ts b/requester/src/app.ts index 3e2f97c..2d61cf1 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -1,98 +1,120 @@ -import { - getRandomClientAutoConfiguration, - IRandomClient, - RandomClient, - RandomClientConfig, -} from "ao-process-clients"; - -const PROVIDER_ID = "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE"; -const RETRY_DELAY_MS = 5000; -const CHANCE_TO_CALL_RANDOM = 1; - -const RANDOM_CONFIG: RandomClientConfig = { - tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, - processId: "vgH7EXVs6-vxxilja6lkBruHlgOkyqddFVg-BVp3eJc", - wallet: JSON.parse(process.env.REQUEST_WALLET_JSON!), - environment: "mainnet", -}; - -let totalRandomCalled = 0; -let totalTimeToFulfill = 0; -let fulfilledRequests = 0; -const outstandingRequests: Set = new Set(); - -async function main() { - const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG); - - while (true) { - console.log("Running") - try { - // Roll for random chance to make a request - if (Math.random() < CHANCE_TO_CALL_RANDOM) { - console.log("Initiating random request..."); - const callbackId = `callback-${Date.now()}`; - await randclient.createRequest([PROVIDER_ID], 1, callbackId); - totalRandomCalled++; - console.log("Random request initiated. Awaiting request ID in open requests..."); - } - - // // Check open requests - // const openRequestsResponse = await randclient.getOpenRandomRequests(PROVIDER_ID); - // const openRequestIds = openRequestsResponse.activeRequests.request_ids || []; - // console.log("Open requests:", openRequestIds); - - // // Track outstanding requests - // for (const requestId of openRequestIds) { - // if (!outstandingRequests.has(requestId)) { - // console.log(`Tracking new request: ${requestId}`); - // outstandingRequests.add(requestId); - // } - // } - - // // Check the status of outstanding requests - // if (outstandingRequests.size > 0) { - // const randomRequestsResponse = await randclient.getRandomRequests(Array.from(outstandingRequests)); - // const requests = randomRequestsResponse.randomRequestResponses || []; // Adjust based on actual response structure - // console.log(randomRequestsResponse) - // console.log(requests) - - // // for (const request of requests) { - // // const requestId = request.requestId; // Adjust if property has a different name - // // if (request?.status === "fulfilled") { - // // const fulfilledTime = Date.now(); - // // const timeToFulfill = fulfilledTime - request.createdTime; // Adjust if createdTime exists - // // totalTimeToFulfill += timeToFulfill; - // // fulfilledRequests++; - // // console.log(`Request ${requestId} fulfilled. Time to fulfill: ${timeToFulfill}ms`); - // // outstandingRequests.delete(requestId); // Stop tracking fulfilled requests - // // } else { - // // console.log(`Request ${requestId} is still being processed.`); - // // } - // // } - // } - - // // Calculate and log stats - // if (fulfilledRequests > 0) { - // const avgTimeToFulfill = totalTimeToFulfill / fulfilledRequests; - - // console.log(` - // Total Random Called: ${totalRandomCalled} - // Outstanding Requests: ${outstandingRequests.size} - // Average Time to Fulfill: ${avgTimeToFulfill}ms - // `); - // } - - // Wait before next cycle - await delay(RETRY_DELAY_MS); - } catch (error) { - console.error("An error occurred:", error); - } - } -} - -function delay(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -// Call the main function +import { + getRandomClientAutoConfiguration, + IRandomClient, + RandomClient, + RandomClientConfig, +} from "ao-process-clients"; + +const PROVIDER_IDS = [ + "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE", + "vgH7EXVs6-vxxilja6lkBruHlgOkyqddFVg-BVp3eJc", + "provider3id" // Replace with actual third provider ID when available +]; + +const RETRY_DELAY_MS = 5000; +const CHANCE_TO_CALL_RANDOM = 1; + +const RANDOM_CONFIG: RandomClientConfig = { + tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, + processId: "vgH7EXVs6-vxxilja6lkBruHlgOkyqddFVg-BVp3eJc", + wallet: JSON.parse(process.env.REQUEST_WALLET_JSON!), + environment: "mainnet", +}; + +let totalRandomCalled = 0; +let totalTimeToFulfill = 0; +let fulfilledRequests = 0; +const outstandingRequests: Set = new Set(); + +function getRandomProviders(): { providers: string[], count: number } { + // Randomly select how many providers we want (1-3) + const count = Math.floor(Math.random() * 3) + 1; + + // Shuffle the provider array and take the first 'count' elements + const shuffled = [...PROVIDER_IDS] + .sort(() => Math.random() - 0.5) + .slice(0, Math.min(count, PROVIDER_IDS.length)); + + return { + providers: shuffled, + count: shuffled.length + }; +} + +async function main() { + const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG); + + while (true) { + console.log("Running") + try { + // Roll for random chance to make a request + if (Math.random() < CHANCE_TO_CALL_RANDOM) { + console.log("Initiating random request..."); + const callbackId = `callback-${Date.now()}`; + const { providers, count } = getRandomProviders(); + console.log(`Selected ${count} providers:`, providers); + await randclient.createRequest(providers, count, callbackId); + totalRandomCalled++; + console.log("Random request initiated. Awaiting request ID in open requests..."); + } + + // // Check open requests + // const openRequestsResponse = await randclient.getOpenRandomRequests(PROVIDER_IDS[0]); + // const openRequestIds = openRequestsResponse.activeRequests.request_ids || []; + // console.log("Open requests:", openRequestIds); + + // // Track outstanding requests + // for (const requestId of openRequestIds) { + // if (!outstandingRequests.has(requestId)) { + // console.log(`Tracking new request: ${requestId}`); + // outstandingRequests.add(requestId); + // } + // } + + // // Check the status of outstanding requests + // if (outstandingRequests.size > 0) { + // const randomRequestsResponse = await randclient.getRandomRequests(Array.from(outstandingRequests)); + // const requests = randomRequestsResponse.randomRequestResponses || []; // Adjust based on actual response structure + // console.log(randomRequestsResponse) + // console.log(requests) + + // // for (const request of requests) { + // // const requestId = request.requestId; // Adjust if property has a different name + // // if (request?.status === "fulfilled") { + // // const fulfilledTime = Date.now(); + // // const timeToFulfill = fulfilledTime - request.createdTime; // Adjust if createdTime exists + // // totalTimeToFulfill += timeToFulfill; + // // fulfilledRequests++; + // // console.log(`Request ${requestId} fulfilled. Time to fulfill: ${timeToFulfill}ms`); + // // outstandingRequests.delete(requestId); // Stop tracking fulfilled requests + // // } else { + // // console.log(`Request ${requestId} is still being processed.`); + // // } + // // } + // } + + // // Calculate and log stats + // if (fulfilledRequests > 0) { + // const avgTimeToFulfill = totalTimeToFulfill / fulfilledRequests; + + // console.log(` + // Total Random Called: ${totalRandomCalled} + // Outstanding Requests: ${outstandingRequests.size} + // Average Time to Fulfill: ${avgTimeToFulfill}ms + // `); + // } + + // Wait before next cycle + await delay(RETRY_DELAY_MS); + } catch (error) { + console.error("An error occurred:", error); + } + } +} + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +// Call the main function main(); \ No newline at end of file From fbde2b62e258196d0e2c02099c143956a5ae1f59 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 17 Jan 2025 15:19:56 -0500 Subject: [PATCH 11/51] finished and working --- .env.template | 23 ++ .gitignore | 2 +- 2docker-compose.yml | 81 +++++++ docker-compose.yml | 199 +++++++++++++--- orchestrator/docs/development.md | 6 +- orchestrator/package.json | 2 +- orchestrator/src/app.ts | 383 +++++++++++++++++++----------- orchestrator/src/clear_outputs.ts | 61 +++++ orchestrator/src/db_config.ts | 7 + orchestrator/src/reset_db.ts | 50 ++++ orchestrator/tsconfig.json | 9 +- randwallet.json | 11 + requester/package.json | 2 +- requester/src/app.ts | 10 +- requestwall.json | 11 + 15 files changed, 664 insertions(+), 193 deletions(-) create mode 100644 .env.template create mode 100644 2docker-compose.yml create mode 100644 orchestrator/src/clear_outputs.ts create mode 100644 orchestrator/src/db_config.ts create mode 100644 orchestrator/src/reset_db.ts create mode 100644 randwallet.json create mode 100644 requestwall.json diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..b741487 --- /dev/null +++ b/.env.template @@ -0,0 +1,23 @@ +# Instance 1 +DB_USER_1=myuser1 +DB_PASSWORD_1=mypassword1 +DB_NAME_1=mydatabase1 +WALLET_JSON_1=your_wallet_json_1 +REQUEST_WALLET_JSON_1=your_request_wallet_json_1 +PROVIDER_ID_1=your_provider_id_1 + +# Instance 2 +DB_USER_2=myuser2 +DB_PASSWORD_2=mypassword2 +DB_NAME_2=mydatabase2 +WALLET_JSON_2=your_wallet_json_2 +REQUEST_WALLET_JSON_2=your_request_wallet_json_2 +PROVIDER_ID_2=your_provider_id_2 + +# Instance 3 +DB_USER_3=myuser3 +DB_PASSWORD_3=mypassword3 +DB_NAME_3=mydatabase3 +WALLET_JSON_3=your_wallet_json_3 +REQUEST_WALLET_JSON_3=your_request_wallet_json_3 +PROVIDER_ID_3=your_provider_id_3 diff --git a/.gitignore b/.gitignore index 16af4e4..8089ddd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ venv/ .env env wallet.json - +wallet*.json # Ignore distribution/build directories dist/ build/ diff --git a/2docker-compose.yml b/2docker-compose.yml new file mode 100644 index 0000000..df93961 --- /dev/null +++ b/2docker-compose.yml @@ -0,0 +1,81 @@ +version: '3.8' + +services: + postgres: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER:-myuser} + POSTGRES_PASSWORD: ${DB_PASSWORD:-mypassword} + POSTGRES_DB: ${DB_NAME:-mydatabase} + ports: + - "5432:5432" + networks: + - backend + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-myuser} -d ${DB_NAME:-mydatabase}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator: + image: randao/orchestrator:v0.1.78 + depends_on: + postgres: + condition: service_healthy + environment: + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: ${DB_USER:-myuser} + DB_PASSWORD: ${DB_PASSWORD:-mypassword} + DB_NAME: ${DB_NAME:-mydatabase} + ENVIRONMENT: local + PATH_TO_WALLET: /app/wallet.json # Path inside the container + WALLET_JSON: ${WALLET_JSON} + PROVIDER_ID: ${PROVIDER_ID} + DOCKER_NETWORK: backend # Passing the network name + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock # Mount Docker socket + - ./wallet.json:/app/wallet.json # Mount wallet.json into the container + + requester: + image: randao/requester:v0.1.13 + environment: + PATH_TO_WALLET: /app/wallet.json # Path inside the container + REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} + PROVIDER_ID: ${PROVIDER_ID} + DOCKER_NETWORK: backend # Passing the network name + networks: + - backend + volumes: + - ./wallet.json:/app/wallet.json # Mount wallet.json into the container + + dbeaver: + image: dbeaver/cloudbeaver:23.2.0 + environment: + CB_SERVER_SERVER_PORT: 8080 + CB_SERVER_ADMIN_NAME: "admin" + CB_SERVER_ADMIN_PASSWORD: "admin123" + depends_on: + postgres: + condition: service_healthy + networks: + - backend + ports: + - "8081:8978" # Expose the DBeaver web UI + volumes: + - dbeaver-data:/opt/cloudbeaver/workspace + +networks: + backend: + name: backend # This will set the network name explicitly + driver: bridge + +volumes: + pgdata: + driver: local + dbeaver-data: + driver: local diff --git a/docker-compose.yml b/docker-compose.yml index 40d406d..46aa88f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,81 +1,202 @@ version: '3.8' services: - postgres: + # Instance 1 + postgres1: image: postgres:13 environment: - POSTGRES_USER: ${DB_USER:-myuser} - POSTGRES_PASSWORD: ${DB_PASSWORD:-mypassword} - POSTGRES_DB: ${DB_NAME:-mydatabase} + POSTGRES_USER: ${DB_USER_1:-myuser} + POSTGRES_PASSWORD: ${DB_PASSWORD_1:-mypassword} + POSTGRES_DB: ${DB_NAME_1:-mydatabase} ports: - "5432:5432" networks: - - backend + - backend1 volumes: - - pgdata:/var/lib/postgresql/data + - pgdata1:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-myuser} -d ${DB_NAME:-mydatabase}"] + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_1:-myuser} -d ${DB_NAME_1:-mydatabase}"] interval: 10s timeout: 5s retries: 5 - orchestrator: - image: randao/orchestrator:v0.1.78 + orchestrator1: + image: randao/orchestrator:v0.2.10 depends_on: - postgres: + postgres1: condition: service_healthy environment: - DB_HOST: postgres + DB_HOST: postgres1 DB_PORT: 5432 - DB_USER: ${DB_USER:-myuser} - DB_PASSWORD: ${DB_PASSWORD:-mypassword} - DB_NAME: ${DB_NAME:-mydatabase} + DB_USER: ${DB_USER_1:-myuser} + DB_PASSWORD: ${DB_PASSWORD_1:-mypassword} + DB_NAME: ${DB_NAME_1:-mydatabase} ENVIRONMENT: local - PATH_TO_WALLET: /app/wallet.json # Path inside the container - WALLET_JSON: ${WALLET_JSON} - PROVIDER_ID: ${PROVIDER_ID} - DOCKER_NETWORK: backend # Passing the network name + WALLET_JSON: ${WALLET_JSON_1} + PROVIDER_ID: ${PROVIDER_ID_1} + DOCKER_NETWORK: backend1 networks: - - backend + - backend1 volumes: - - /var/run/docker.sock:/var/run/docker.sock # Mount Docker socket - - ./wallet.json:/app/wallet.json # Mount wallet.json into the container + - /var/run/docker.sock:/var/run/docker.sock - requester: - image: randao/requester:v0.1.13 + dbeaver1: + image: dbeaver/cloudbeaver:23.2.0 environment: - PATH_TO_WALLET: /app/wallet.json # Path inside the container - REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} - PROVIDER_ID: ${PROVIDER_ID} - DOCKER_NETWORK: backend # Passing the network name + CB_SERVER_SERVER_PORT: 8080 + CB_SERVER_ADMIN_NAME: "admin1" + CB_SERVER_ADMIN_PASSWORD: "admin123" + depends_on: + postgres1: + condition: service_healthy + networks: + - backend1 + ports: + - "8081:8978" + volumes: + - dbeaver-data1:/opt/cloudbeaver/workspace + + # Instance 2 + postgres2: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_2:-myuser} + POSTGRES_PASSWORD: ${DB_PASSWORD_2:-mypassword} + POSTGRES_DB: ${DB_NAME_2:-mydatabase} + ports: + - "5433:5432" + networks: + - backend2 + volumes: + - pgdata2:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_2:-myuser} -d ${DB_NAME_2:-mydatabase}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator2: + image: randao/orchestrator:v0.2.10 + depends_on: + postgres2: + condition: service_healthy + environment: + DB_HOST: postgres2 + DB_PORT: 5432 + DB_USER: ${DB_USER_2:-myuser} + DB_PASSWORD: ${DB_PASSWORD_2:-mypassword} + DB_NAME: ${DB_NAME_2:-mydatabase} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_2} + PROVIDER_ID: ${PROVIDER_ID_2} + DOCKER_NETWORK: backend2 + networks: + - backend2 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + dbeaver2: + image: dbeaver/cloudbeaver:23.2.0 + environment: + CB_SERVER_SERVER_PORT: 8080 + CB_SERVER_ADMIN_NAME: "admin2" + CB_SERVER_ADMIN_PASSWORD: "admin123" + depends_on: + postgres2: + condition: service_healthy networks: - - backend + - backend2 + ports: + - "8082:8978" volumes: - - ./wallet.json:/app/wallet.json # Mount wallet.json into the container + - dbeaver-data2:/opt/cloudbeaver/workspace - dbeaver: + # Instance 3 + postgres3: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_3:-myuser} + POSTGRES_PASSWORD: ${DB_PASSWORD_3:-mypassword} + POSTGRES_DB: ${DB_NAME_3:-mydatabase} + ports: + - "5434:5432" + networks: + - backend3 + volumes: + - pgdata3:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_3:-myuser} -d ${DB_NAME_3:-mydatabase}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator3: + image: randao/orchestrator:v0.2.10 + depends_on: + postgres3: + condition: service_healthy + environment: + DB_HOST: postgres3 + DB_PORT: 5432 + DB_USER: ${DB_USER_3:-myuser} + DB_PASSWORD: ${DB_PASSWORD_3:-mypassword} + DB_NAME: ${DB_NAME_3:-mydatabase} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_3} + PROVIDER_ID: ${PROVIDER_ID_3} + DOCKER_NETWORK: backend3 + networks: + - backend3 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + dbeaver3: image: dbeaver/cloudbeaver:23.2.0 environment: CB_SERVER_SERVER_PORT: 8080 - CB_SERVER_ADMIN_NAME: "admin" + CB_SERVER_ADMIN_NAME: "admin3" CB_SERVER_ADMIN_PASSWORD: "admin123" depends_on: - postgres: + postgres3: condition: service_healthy networks: - - backend + - backend3 ports: - - "8081:8978" # Expose the DBeaver web UI + - "8083:8978" volumes: - - dbeaver-data:/opt/cloudbeaver/workspace + - dbeaver-data3:/opt/cloudbeaver/workspace + + requester: + image: randao/requester:v0.1.20 + environment: + REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} + + requesterfast: + image: randao/requester-fast:v0.1.0 + environment: + REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} networks: - backend: - name: backend # This will set the network name explicitly + backend1: + name: backend1 + driver: bridge + backend2: + name: backend2 + driver: bridge + backend3: + name: backend3 driver: bridge volumes: - pgdata: + pgdata1: + driver: local + pgdata2: + driver: local + pgdata3: + driver: local + dbeaver-data1: + driver: local + dbeaver-data2: driver: local - dbeaver-data: + dbeaver-data3: driver: local diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index e1a2d70..35158c0 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -2,4 +2,8 @@ To build: Save all files Run: -docker build -t randao/orchestrator:latest -t randao/orchestrator:v0.1.10 . \ No newline at end of file +docker build -t randao/orchestrator:latest -t randao/orchestrator:v0.1.10 . + +docker inspect -f '{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -q) + +npx ts-node src/clear_outputs.ts \ No newline at end of file diff --git a/orchestrator/package.json b/orchestrator/package.json index d1ef1d0..f5b241a 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients":"3.4.2", + "ao-process-clients":"3.5.15", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index ea542dd..fc1430e 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -2,10 +2,11 @@ import { Client } from 'pg'; import Docker from 'dockerode'; import AWS from 'aws-sdk'; import { getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomClientConfig } from "ao-process-clients" +import { dbConfig } from './db_config.js'; const RANDOM_CONFIG: RandomClientConfig = { - tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, - processId: "vgH7EXVs6-vxxilja6lkBruHlgOkyqddFVg-BVp3eJc", + tokenProcessId: "7enZBOhWsyU3A5oCt8HtMNNPHSxXYJVTlOGOetR9IDw", + processId: "KbaY8P4h9wdHYKHlBSLbXN_yd-9gxUDxSgBackUxTiQ", wallet: JSON.parse(process.env.WALLET_JSON!), environment: 'mainnet' } @@ -14,14 +15,7 @@ const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG) const docker = new Docker(); -// Database configuration -const dbConfig = { - host: process.env.DB_HOST || 'localhost', - port: parseInt(process.env.DB_PORT || '5432', 10), - user: process.env.DB_USER || 'myuser', - password: process.env.DB_PASSWORD || 'mypassword', - database: process.env.DB_NAME || 'mydatabase', -}; + // Constants for configuration const POLLING_INTERVAL_MS = 5000; @@ -42,9 +36,12 @@ const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks const ongoingContainers = new Set(); // Track container IDs of running Docker containers const ongoingFulfillments = new Set(); // Track request IDs for ongoing fulfillments const PROVIDER_ID = process.env.PROVIDER_ID || "0"; +const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "backend"; let ongoingRequest = false; let spotInterruptions = 0; let totalProvided = 0; +let PreviousTotalAvailableRandom = 0; + // Retry logic for connecting to PostgreSQL async function connectWithRetry(): Promise { const client = new Client(dbConfig); @@ -140,7 +137,7 @@ async function triggerVDFJobPod(): Promise { return null; } } else { - const containerName = `vdf_job_${Date.now()}`; + const containerName = `vdf_job_${Date.now()}_${Math.floor(Math.random() * 10000)}`; console.log(`Starting Docker container with name: ${containerName}`); const container = await docker.createContainer({ Image: VDF_JOB_IMAGE, @@ -154,7 +151,7 @@ async function triggerVDFJobPod(): Promise { `DATABASE_NAME=${dbConfig.database}`, ], HostConfig: { - NetworkMode: 'backend', + NetworkMode: DOCKER_NETWORK, }, name: containerName }); @@ -243,7 +240,7 @@ function hexMod64Bit(expectedOutput: string): { expectedOutput64BitBase10: strin const number = BigInt(`0x${expectedOutput}`); // Define the 64-bit modulus (2^64 - 1) - const modulus = BigInt("0xFFFFFFFFFFFFFFFF"); + const modulus = BigInt("0x7FFFFFFFF"); // Keep dividing by modulus until we get a remainder less than modulus let remainder = number; @@ -267,40 +264,36 @@ async function checkAndFetchIfNeeded(client: Client): Promise { try { if (ongoingRequest) return; const res = await client.query('SELECT COUNT(*) AS count FROM verifiable_delay_functions WHERE request_id IS NULL'); - const currentCount = parseInt(res.rows[0].count, 10); // Parse the count as an integer + const currentCount = parseInt(res.rows[0].count, 10); console.log("Total usable db entries: " + currentCount); - // Only try to update available values if we have some entries - if (currentCount > 0) { - try { - const updateAvailableValuesResult = await randclient.updateProviderAvailableValues(currentCount); - console.log("Updates onchain: " + updateAvailableValuesResult); - console.log(await randclient.getProviderAvailableValues(PROVIDER_ID)); - } catch (error) { - console.log("Warning: Could not update available values:", error); - } - } - if (currentCount < MINIMUM_ENTRIES) { const entriesNeeded = TARGET_ENTRIES - currentCount; console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries to reach ${TARGET_ENTRIES}...`); ongoingRequest = true; - let tasksTriggered = 0; - - while (tasksTriggered < entriesNeeded) { - if (ENVIRONMENT === 'cloud' && ongoingTasks.size >= MAX_OUTSTANDING_REQUESTS) { - await monitorECSTasks(); - } else if (ENVIRONMENT !== 'cloud' && ongoingContainers.size >= MAX_OUTSTANDING_REQUESTS) { - await monitorDockerContainers(); - } else { - const taskArn = await triggerVDFJobPod(); - if (taskArn) { - tasksTriggered++; - console.log(`Task triggered: ${taskArn}. Total ongoing tasks: ${ongoingTasks.size}. Total ongoing containers ${+ongoingContainers.size}`); - } + + const batchCount = Math.min(entriesNeeded, MAX_OUTSTANDING_REQUESTS); + console.log(`Batchcount: ${batchCount}, Ongoing containers: ${ongoingContainers.size}`); + + let spawnCount = Math.min(batchCount, MAX_OUTSTANDING_REQUESTS - ongoingContainers.size); + if (spawnCount <= 0) { + console.log("Max outstanding containers reached. Skipping new container launches."); + return; + } + + for (let i = 0; i < spawnCount; i++) { + if (ENVIRONMENT === 'cloud' && ongoingTasks.size < MAX_OUTSTANDING_REQUESTS) { + triggerVDFJobPod().then(taskArn => { + if (taskArn) console.log(`ECS task triggered: ${taskArn}`); + }).catch(console.error); + } else if (ENVIRONMENT !== 'cloud' && ongoingContainers.size < MAX_OUTSTANDING_REQUESTS) { + triggerVDFJobPod().then(containerId => { + if (containerId) console.log(`Docker container triggered: ${containerId}`); + }).catch(console.error); } } - ongoingRequest = false; + + ongoingRequest = false; // Immediately allow other operations } } catch (error) { console.error('Error during check and fetch:', error); @@ -308,51 +301,40 @@ async function checkAndFetchIfNeeded(client: Client): Promise { } } -// Function to clear all output requests -async function clearAllOutputRequests(client: Client): Promise { +// Function to post VDF challenge (fetches dbId dynamically) +async function fulfillRandomChallenge(client: Client, requestId: string): Promise { try { - // Fetch the current open output requests - const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); - console.log("Open requests fetched:", openRequests); - - if (openRequests && openRequests.activeOutputRequests) { - openRequests.activeOutputRequests.request_ids.forEach((requestId) => { - console.log(`Sending "No data" for output request ID: ${requestId}`); - - // Send "No data" as output for each request (do not await, fire and forget) - randclient.postVDFOutputAndProof(requestId, "No data", "No data").then(() => { - console.log(`"No data" sent for request ID: ${requestId}`); - }).catch((error) => { - console.error(`Error sending "No data" for request ID: ${requestId}:`, error); - }); - }); - } else { - console.log('No Output requests to clear'); + // Fetch the necessary details from the database using requestId + const res = await client.query( + `SELECT id, modulus, input + FROM verifiable_delay_functions + WHERE request_id = $1`, + [requestId] + ); + + if (!res.rowCount) { + console.error(`No entry found for Request ID: ${requestId}`); + return; } - } catch (error) { - console.error('An error occurred while clearing output requests:', error); - } -} -// Function to post VDF challenge -async function fulfillRandomChallenge(client: Client, dbId: string, requestId: string, modulus: string, input: string): Promise { - if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) return; + const { id: dbId, modulus, input } = res.rows[0]; - try { - console.log(`Received entry details - ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); + console.log(`Fetched entry details - Request ID: ${requestId}, DB ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); // Add hex prefix to modulus and input const hexModulus = addHexPrefix(modulus); const hexInput = addHexPrefix(input); - console.log(`Posting VDF challenge for entry ID: ${dbId}, request ID: ${requestId}`); + console.log(`Posting VDF challenge for Request ID: ${requestId}, DB ID: ${dbId}`); await randclient.postVDFChallenge(requestId, hexModulus, hexInput); - console.log(`Challenge posted for request ID: ${requestId}. Waiting to post proof...`); + console.log(`Challenge posted for Request ID: ${requestId}. Waiting to post proof...`); } catch (error) { - console.error(`Error posting VDF challenge for request ID: ${requestId}:`, error); + console.error(`Error posting VDF challenge for Request ID: ${requestId}:`, error); } } + + // Function to post VDF output and proof async function fulfillRandomOutput(client: Client, requestId: string): Promise { try { @@ -386,93 +368,199 @@ async function fulfillRandomOutput(client: Client, requestId: string): Promise { - console.log("Starting Polling..."); - await checkAndFetchIfNeeded(client); + if (pollingInProgress) { + console.log(`[SKIPPED] Polling is already in progress. Skipping this run.`); + return; // Prevent concurrent execution + } - try { - console.log(PROVIDER_ID); + pollingInProgress = true; // Mark polling as in progress + const logId = getLogId(); + console.log(`${logId} Starting Polling...`); - // Part 1: Fetch open requests + try { + console.log(`${logId} Step 1: Fetching open requests from the Randomness Client.`); const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); - console.log("Open requests fetched:", openRequests); - - if (false) { - clearAllOutputRequests(client); - } else { - - // Part 2: Fulfill Challenge requests - if (openRequests && openRequests.activeChallengeRequests) { - for (const requestId of openRequests.activeChallengeRequests.request_ids) { - console.log("Max outstanding is " + MAX_OUTSTANDING_FULFILLMENTS) - if (ongoingFulfillments.size >= MAX_OUTSTANDING_FULFILLMENTS) break; - try { - // Fetch modulus and input from the database - const res = await client.query('SELECT * FROM verifiable_delay_functions WHERE request_id IS NULL ORDER BY id ASC LIMIT 1'); - if (!res.rowCount) { - console.error(`No available entry found in the database.`); - return; - } - const { id: dbId, modulus, input } = res.rows[0]; - - console.log(`Fetched entry from database - ID: ${dbId}, RequestID: ${requestId}`); - // Update the database to save the requestId with the entry - await client.query('UPDATE verifiable_delay_functions SET request_id = $1 WHERE id = $2', [requestId, dbId]); - console.log(`Updated database entry with request ID: ${requestId} for entry ID: ${dbId}`); - ongoingFulfillments.add(dbId); - console.log(`Processing challenge request ID: ${requestId}`); - await fulfillRandomChallenge(client, dbId, requestId, modulus, input); - } catch (error) { - console.log(error); - } - } - } else { - console.log('No Challenge requests'); - } + console.log(`${logId} Step 1: Open Requests: ${JSON.stringify(openRequests)}`); + console.log(openRequests); + console.log(`${logId} Step 1: Open requests fetched.`); + + // Run Step 2, 3, and 4 concurrently after Step 1 + await Promise.all([ + processChallengeRequests(client, openRequests.activeChallengeRequests, logId), + processOutputRequests(client, openRequests.activeOutputRequests, logId), + cleanupFulfilledEntries(client, openRequests, logId) + ]); + + console.log(`${logId} Polling cycle completed successfully.`); + } catch (error) { + console.error(`${logId} An error occurred during polling:`, error); + } finally { + pollingInProgress = false; // Reset flag after execution + } +} - // Part 3: Fulfill Output requests - if (openRequests && openRequests.activeOutputRequests) { - for (const requestId of openRequests.activeOutputRequests.request_ids) { - console.log(`Processing output request ID: ${requestId}`); - fulfillRandomOutput(client, requestId); - } - } else { - console.log('No Output requests'); - } +// Step 2: Process Challenge Requests (Database selection & assigning is atomic) +async function processChallengeRequests( + client: Client, + activeChallengeRequests: { request_ids: string[] } | undefined, + parentLogId: string +): Promise { + const logId = getLogId(); + console.log(`${logId} Step 2: Processing challenge requests.`); - // Part 4: Check for completed fulfillments that are no longer in the challenge or output list - const noLongerUsedIds: string[] = []; - for (const ongoingId of ongoingFulfillments) { - const challengeInProgress = openRequests.activeChallengeRequests?.request_ids.includes(ongoingId); - const outputInProgress = openRequests.activeOutputRequests?.request_ids.includes(ongoingId); + if (!activeChallengeRequests || activeChallengeRequests.request_ids.length === 0) { + console.log(`${logId} No Challenge Requests to process.`); + return; + } - console.log(`Checking ID: ${ongoingId}`); - console.log(` - In active challenges: ${challengeInProgress}`); - console.log(` - In active outputs: ${outputInProgress}`); + // Limit to MAX_OUTSTANDING_FULFILLMENTS requests + const requestIds = activeChallengeRequests.request_ids.slice(0, MAX_OUTSTANDING_FULFILLMENTS); + console.log(`${logId} Processing up to ${requestIds.length} requests.`); - if (!challengeInProgress && !outputInProgress) { - noLongerUsedIds.push(ongoingId); - } - } + try { + await client.query('BEGIN'); // Start transaction + + console.log(`${logId} Fetching existing request mappings.`); + // Fetch already assigned request_id -> dbId mappings + const existingMappingsRes = await client.query( + `SELECT request_id FROM verifiable_delay_functions + WHERE request_id = ANY($1) + FOR UPDATE SKIP LOCKED`, + [requestIds] + ); - // Log all IDs that are no longer in use in a single line - if (noLongerUsedIds.length > 0) { - console.log(`No longer in use: ${noLongerUsedIds.join(', ')}`); - // Optionally remove them from the ongoing fulfillments list if necessary - for (const id of noLongerUsedIds) { - console.log("Removing id from list:" + id) - //await client.query('DELETE FROM verifiable_delay_functions WHERE id = $1', [id]); - ongoingFulfillments.delete(id); - } - } + const existingRequestIds = new Set(existingMappingsRes.rows.map(row => row.request_id)); + console.log(`${logId} Found ${existingRequestIds.size} already mapped requests.`); + + // Find only the unmapped requests (requestIds not in existingRequestIds) + const unmappedRequestIds = requestIds.filter(requestId => !existingRequestIds.has(requestId)); + console.log(`${logId} Unmapped requests: ${unmappedRequestIds.length}`); + + // Fetch available DB entries for unmapped requests + console.log(`${logId} Fetching available DB entries.`); + const dbRes = await client.query( + `SELECT id FROM verifiable_delay_functions + WHERE request_id IS NULL + ORDER BY id ASC + LIMIT $1 + FOR UPDATE SKIP LOCKED`, + [unmappedRequestIds.length] + ); + + const availableDbEntries = dbRes.rows.map(row => row.id); + console.log(`${logId} Found ${availableDbEntries.length} available DB entries.`); + + // Reduce request list if we don’t have enough DB entries + if (availableDbEntries.length < unmappedRequestIds.length) { + console.log(`${logId} Limiting requests to ${availableDbEntries.length} due to DB availability.`); + unmappedRequestIds.length = availableDbEntries.length; + } + + if (availableDbEntries.length === 0 && existingRequestIds.size === 0) { + console.log(`${logId} No available DB entries to process and no existing mappings.`); + await client.query('COMMIT'); // Commit to release locks + return; + } + + // Map unmapped requestIds to available DB entries (1:1) + for (let i = 0; i < unmappedRequestIds.length; i++) { + await client.query( + `UPDATE verifiable_delay_functions + SET request_id = $1 + WHERE id = $2`, + [unmappedRequestIds[i], availableDbEntries[i]] + ); + console.log(`${logId} Assigned Request ID ${unmappedRequestIds[i]} to DB Entry ${availableDbEntries[i]}.`); } + + await client.query('COMMIT'); // Commit all updates at once + + // Call fulfillRandomChallenge for all request IDs (existing + newly mapped) + for (const requestId of requestIds) { + fulfillRandomChallenge(client, requestId) + .catch(error => console.error(`${logId} Error fulfilling challenge for Request ID ${requestId}:`, error)); + } + + console.log(`${logId} Step 2 completed.`); } catch (error) { - console.error('An error occurred while fetching open random requests:', error); + console.error(`${logId} Error in processChallengeRequests:`, error); + await client.query('ROLLBACK'); // Rollback on failure } } + + + + +// Step 3: Process Output Requests (unchanged but with logging) +async function processOutputRequests( + client: Client, + activeOutputRequests: { request_ids: string[] } | undefined, + parentLogId: string +): Promise { + const logId = getLogId(); + console.log(`${logId} Step 3: Processing output requests.`); + + if (!activeOutputRequests || activeOutputRequests.request_ids.length === 0) { + console.log(`${logId} No Output Requests to process.`); + return; + } + + const outputPromises = activeOutputRequests.request_ids.map(async (requestId) => { + console.log(`${logId} Processing output request ID: ${requestId}`); + + // Run fulfillRandomOutput asynchronously (do not await) + fulfillRandomOutput(client, requestId) + .catch(error => console.error(`${logId} Error fulfilling output:`, error)); + }); + + await Promise.all(outputPromises); + console.log(`${logId} Step 3 completed.`); +} + +// Step 4: Remove fulfilled entries no longer in use (unchanged but with logging) +async function cleanupFulfilledEntries( + client: Client, + openRequests: any, + parentLogId: string +): Promise { + const logId = getLogId(); + console.log(`${logId} Step 4: Checking for fulfilled entries no longer in use.`); + + const noLongerUsedIds: string[] = []; + for (const ongoingId of ongoingFulfillments) { + const challengeInProgress = openRequests.activeChallengeRequests?.request_ids.includes(ongoingId); + const outputInProgress = openRequests.activeOutputRequests?.request_ids.includes(ongoingId); + + if (!challengeInProgress && !outputInProgress) { + noLongerUsedIds.push(ongoingId); + } + } + + if (noLongerUsedIds.length > 0) { + console.log(`${logId} No longer in use: ${noLongerUsedIds.join(', ')}`); + noLongerUsedIds.forEach((id) => { + ongoingFulfillments.delete(id); + console.log(`${logId} Removed ID ${id} from ongoing fulfillments.`); + }); + } else { + console.log(`${logId} No fulfilled entries to remove.`); + } + + console.log(`${logId} Step 4 completed.`); +} + + // Main function async function run(): Promise { const client = await connectWithRetry(); @@ -481,8 +569,19 @@ async function run(): Promise { setInterval(async () => { const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); console.log(`Periodic log - Current database size: ${res.rows[0].count}`); + // Check and fetch entries for the database if needed + console.log("Step 1: Checking and fetching database entries if below threshold."); + checkAndFetchIfNeeded(client).catch((error) => { + console.error("Error in checkAndFetchIfNeeded:", error); + }); + }, 10000); + setInterval(async () => { + await monitorDockerContainers(); + await monitorECSTasks(); + }, 30000); // Cleanup every 30 seconds + setInterval(async () => { await polling(client); }, POLLING_INTERVAL_MS); diff --git a/orchestrator/src/clear_outputs.ts b/orchestrator/src/clear_outputs.ts new file mode 100644 index 0000000..d7ee4c3 --- /dev/null +++ b/orchestrator/src/clear_outputs.ts @@ -0,0 +1,61 @@ +import { Client } from 'pg'; +import { getRandomClientAutoConfiguration, RandomClient, RandomClientConfig } from "ao-process-clients"; +import { dbConfig } from './db_config'; + +// Random Client Configuration +const RANDOM_CONFIG: RandomClientConfig = { + tokenProcessId: "7enZBOhWsyU3A5oCt8HtMNNPHSxXYJVTlOGOetR9IDw", + processId: "KbaY8P4h9wdHYKHlBSLbXN_yd-9gxUDxSgBackUxTiQ", + wallet: JSON.parse(process.env.WALLET_JSON!), + environment: 'mainnet' as const +}; + +const randclient = new RandomClient(RANDOM_CONFIG); +const PROVIDER_ID = process.env.PROVIDER_ID || "0"; + +// Function to connect to PostgreSQL +async function connectToDatabase() { + const client = new Client(dbConfig); + await client.connect(); + console.log("Connected to PostgreSQL database."); + return client; +} + +// Function to clear all output requests +async function clearAllOutputRequests(client: Client) { + try { + console.log("Fetching open output requests..."); + const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); + + if (openRequests && openRequests.activeOutputRequests) { + console.log(`Found ${openRequests.activeOutputRequests.request_ids.length} output requests to clear.`); + + // Process each request + const clearPromises = openRequests.activeOutputRequests.request_ids.map(async (requestId: string) => { + console.log(`Sending "No data" for output request ID: ${requestId}`); + try { + await randclient.postVDFOutputAndProof(requestId, "No data", "No data"); + console.log(`"No data" successfully sent for request ID: ${requestId}`); + } catch (error) { + console.error(`Error sending "No data" for request ID: ${requestId}:`, error); + } + }); + + await Promise.all(clearPromises); + console.log("All output requests cleared."); + } else { + console.log("No output requests to clear."); + } + } catch (error) { + console.error("An error occurred while clearing output requests:", error); + } finally { + await client.end(); + console.log("Database connection closed."); + } +} + +// Run the function when the script is executed +(async () => { + const client = await connectToDatabase(); + await clearAllOutputRequests(client); +})(); diff --git a/orchestrator/src/db_config.ts b/orchestrator/src/db_config.ts new file mode 100644 index 0000000..22c50ca --- /dev/null +++ b/orchestrator/src/db_config.ts @@ -0,0 +1,7 @@ +export const dbConfig = { + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT || '5432', 10), + user: process.env.DB_USER || 'myuser', + password: process.env.DB_PASSWORD || 'mypassword', + database: process.env.DB_NAME || 'mydatabase', +}; \ No newline at end of file diff --git a/orchestrator/src/reset_db.ts b/orchestrator/src/reset_db.ts new file mode 100644 index 0000000..ae62d50 --- /dev/null +++ b/orchestrator/src/reset_db.ts @@ -0,0 +1,50 @@ +import { Client, QueryResult } from 'pg'; +import { dbConfig } from './db_config'; + +interface TableRow { + tablename: string; +} + +// Function to connect to the database and drop all tables +async function resetDatabase(): Promise { + console.log("Connecting to PostgreSQL to reset the database..."); + + const client = new Client(dbConfig); + try { + await client.connect(); + console.log("Connected to database. Dropping all tables..."); + + // Disable foreign key constraints (important for dropping tables safely) + await client.query(`SET session_replication_role = 'replica';`); + + // Fetch all tables in the public schema + const tablesRes = await client.query(` + SELECT tablename FROM pg_tables WHERE schemaname = 'public'; + `); + + const tables = tablesRes.rows.map((row: TableRow) => row.tablename); + + if (tables.length === 0) { + console.log("No tables found in the database."); + } else { + // Drop each table + for (const table of tables) { + console.log(`Dropping table: ${table}`); + await client.query(`DROP TABLE IF EXISTS "${table}" CASCADE;`); + } + console.log("All tables dropped successfully."); + } + + // Re-enable foreign key constraints + await client.query(`SET session_replication_role = 'origin';`); + + } catch (error) { + console.error("Error while resetting database:", error); + } finally { + await client.end(); + console.log("Database connection closed."); + } +} + +// Run the reset function +resetDatabase().catch(console.error); diff --git a/orchestrator/tsconfig.json b/orchestrator/tsconfig.json index 493e143..bf50834 100644 --- a/orchestrator/tsconfig.json +++ b/orchestrator/tsconfig.json @@ -5,12 +5,15 @@ "module": "commonjs", "target": "es6", "strict": true, - "esModuleInterop": true + "esModuleInterop": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "declaration": true }, "include": [ "src/**/*.ts" - ], +, "src/db_config.ts", "src/clear_all_output_requests.ts", "src/reset_db.mjs" ], "exclude": [ "node_modules" ] -} \ No newline at end of file +} diff --git a/randwallet.json b/randwallet.json new file mode 100644 index 0000000..e68b8a3 --- /dev/null +++ b/randwallet.json @@ -0,0 +1,11 @@ +{ + "kty": "RSA", + "e": "AQAB", + "n": "yS6M-RRF4Q84Qhf8zr-DCvUMepQDmakGsj9tk1oOJpzWgL_jv3rWW8XtSZpPwQhN_tdiFqAiEgmdqiJxsqjGxez0o7WYL57P_usV-lRteuGPUQe0vaiWG2ls8-Gpi15oTLx-OSs4h8Ggbbd0X73fSK-xsZOgvfsGX7llGyZ0iTd6YQ84y2-cKBmPjdmaRxVhbtm8j4npsgCesmPDaKqJUTTiUhz5yXNfe-SPjELgRcPVGZ-sfgfw6PB5uduLy6iIeWLS_UOEYZRpi6F1wPkd4mJHX58GtJtY4gqJPaSueex-_ig1Q7UEFAjQEs-jaOaZOMZ-oBYhewa4DZwTPb1VcD5HzToPSgDuSZpBMVGYTRJ2-4scGWzk6ZWeUBpStiPe9-HMQgLo9PYh7JbptE6VP5B29YjRkCgzUk9cLCo7DdG6RJwkKlITeyOmjC1jrMecyhFhgahVYmKw8BtfkAHY2qJri06udSlVIIG0URJgecD3Ip9ug15cVQxLNCavnhlqPpeVoSoBDlguPzpLK8NoRB6LXf5C0kFDttARErJcVoRKWj_SlpfGDvbeuXrzQCeD4ijR4muJUzHmeveaNvy4BjPlwiJIMOMFJ1hy35P5QUaRxG37y46iBnYJyZtfs8xkmBboI6SNXxl8DMRlHkeitpRSF9o2JtF2N6zNG06se-M", + "d": "jseq5Vut_HyAwOelVFY2-p_Pq29A3t1HE1pQujM_t8e3tMh7KnpTh-6A6avKzoTGVgcmZkhf5c1NSGIJr3J5IB97wHQw_hsIIzNU9cTVrlBHFosRi0kKv4mi-bM-UNs_yrY8Hva9uBuDcFqzDQAEZ-HJiu3rNunhLC63wv0X2Aq3oArdlAQyH4OEjnfRNoHGFJYTbQvD_8DQ4GpNVQszSZ7uwLgvzvlC2vrrpfAQm3LQ2s6n7QpVv_xrZ6yZGoER2pR4iaZRXa-m-KIuoDYtL23wmbyTVbqq6kHwBP_LlFx0BI0kwFjH_DAE_FTA9MI0HEdDtvn96QJrvbz3Kuokp7JWWDbBFub8c3B6rn88KQRC3wOzAydB8ARcBbnPpkFKpw6fM-CCegc67qsJvwz2oBIcNZqqOD-X4wY7nc7vQ09i8zqD1oEkRdblnBCz4AgEh2ROBtm_kzAqvqDh92vnt0FphsD_BnEYyi_JJE92vscXWgseQj2CM4W5EL9EY1ijf0Nmkkb8DpCxfH6RwnI9BzNnitUvV16M1rxDZqga344vtnlEK7AvntkWRRFCqYE0EU0uBuWYBHKpbvGmnFovpDWXk6qIzSwci3SSMCk_i0GJ8IjJ-mO0x2wQQ7Pd0FkGOepzJq1DTlQakdVsX7LbiOUSs7Y1QTddG5G6PI5m_IE", + "p": "6BHvLYkIs0e6SPgTZwd8HBMChMauUsrro_rA1BtTZpij9a2ONK4Vji5C1_I-Ey_XEi889srF2-yOct8l2uBqZeDwgRf47Zxab3WCfwu2vnf4s7onwyI4UaFDSTCC7BZK11vg0P7vWDywnP4HorcqhAyiuY3If9bS-D_I_Ip8_ivP79qPu4H61Pe6G0zzPh1PiMoW3xdMeJaTU5ElMSiuFNhan_qeNc3EzXdo5udkL29j7pIczGspvoaT07wUd_RfqX3D_Fqdi23ZZRnclSz-BN9yj4J22Fe92IvOw0UM36alNkD-PRbw2c3zFDWgji445IxIDzG_8m6_BnIya2SG5Q", + "q": "3e0-wiXWs8kcf_gxPFFdgliBqa4PdajKZGg80J9MznErL6FJVrFvma4mDvhJdXyVzDyVIZIXH4oefR-RNz2nNycS8-icGQYap3KKZRNEhdD8HbCf3OL0nRffIRXRvfc0xgmLb0ysvXYpklrEPLMuqpvw3pxoSgW4_HZhQXQbkyX-xt9-u0v4DgAcxAiEwvCjee8Ul2iRxai01CesGTDMOivJU_ZpsjirVe94hIEwOW-2fjzX6zXiPJxkvtQbSwRMPzkuIYtXsj4kjf_0I0H6gqifFeVd-LHI6zWuYeoxrMWYFKBb6b3JQ_I6iS7vPgQ7z9obIE0Z7CjrUIysx_ZDJw", + "dp": "tMGzT_5aXnnR6SAAzNERpDRSU-UExsvzOmgHZa5bCaB-pM8n4nRtqa7ytYyjOQKcPDe6_mb2MdRRJ7wTmiYN-Yh5C7QGWdzcu9AFcrtG_ZgoiKTIb77pqvs2k31LnGPIq3GO7HqFJm8vCTj77YtJfEzzOh_rOVe0P1Q_UiT0Mm0hqyrLpTsaimLh_H21QH5IAr2VjvJwx8RQwFhfZajP3sCd5dmo_TNmxLrrZF56tE_IwHviHn6hpxrfbZ4jO0OGd1fUHWzfJUjMeWjpXPAMcvMwIgN2WhANeOt8gq_31QPRzy5UWHTT6HH3kZgrlFMAUVPKlLslTMlh1L2B9A_62Q", + "dq": "1ldqctda65_E7_AFla08NEVJTlm3wrr4Z0up2RDSfN0eic0r6RhMolBpn7G8OUXP5Edq_dZ8kNC0q8KOXZ0lYIZTrtGt2hlkKu_crMyUNO5oYkCR1iQ5f3Rr5CePwPr-tHrJegDDIeX7NsiFmd6xpsQgOtEzhLLMPMIVIsOCUney_98iJsGz3cnL_qX_m8wRCBaae7XafN55cCK0_Et-JHzf4UEwSpqjGMfGTav8qKy1xGz9WcZcMJAYWZrAlY6cGcAfRvSvCY8tfRyFbnwt-H3l0J8MSMNlO49IUnd_7M-XF-zdeP79YauVT6POG8a5AgI0itkMvWO0CsMjqam2pw", + "qi": "xMOtYWsparV6PZR0FqEzafuVpZLCE3Y8NR81f1ZBFUNhZVd4nGSlC5dgozfWJ--Gh9ejyb8-vvJaK2FJCUqODQ-xWQm48jADCOSOmYaM7IR7oqLJGdFWbac-IJm68_53Ql_F6OSrzZtjaphQ0dR5ax4NibE5bTx70eiAnlgdNiijZBmGlfVpMQopb3M3rOSZDtw_WBsSiduIrPIjy2FcMORpOk3849KOHb1nxCWuqWfxtywzxptRuj_RKIHmgqQ2owvJpDCyHbQrgVgqZhT7JnPAcUA7U8hMSl9I-tDlk9YhakZsMtEzQQgOERb7p7_N3d10OE-2VitpH4_Gl_u0Uw" +} \ No newline at end of file diff --git a/requester/package.json b/requester/package.json index e502c8a..eb0bca0 100644 --- a/requester/package.json +++ b/requester/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "3.4.2", + "ao-process-clients": "3.5.15", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/requester/src/app.ts b/requester/src/app.ts index 2d61cf1..8f4dec8 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -7,16 +7,16 @@ import { const PROVIDER_IDS = [ "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE", - "vgH7EXVs6-vxxilja6lkBruHlgOkyqddFVg-BVp3eJc", - "provider3id" // Replace with actual third provider ID when available + "c8Iq4yunDnsJWGSz_wYwQU--O9qeODKHiRdUkQkW2p8", + "Sr3HVH0Nh6iZzbORLpoQFOEvmsuKjXsHswSWH760KAk" ]; -const RETRY_DELAY_MS = 5000; +const RETRY_DELAY_MS = 600; //1 minute const CHANCE_TO_CALL_RANDOM = 1; const RANDOM_CONFIG: RandomClientConfig = { - tokenProcessId: getRandomClientAutoConfiguration().tokenProcessId, - processId: "vgH7EXVs6-vxxilja6lkBruHlgOkyqddFVg-BVp3eJc", + tokenProcessId: "7enZBOhWsyU3A5oCt8HtMNNPHSxXYJVTlOGOetR9IDw", + processId: "KbaY8P4h9wdHYKHlBSLbXN_yd-9gxUDxSgBackUxTiQ", wallet: JSON.parse(process.env.REQUEST_WALLET_JSON!), environment: "mainnet", }; diff --git a/requestwall.json b/requestwall.json new file mode 100644 index 0000000..ba122ef --- /dev/null +++ b/requestwall.json @@ -0,0 +1,11 @@ +{ + "kty": "RSA", + "e": "AQAB", + "n": "mGLG_TCdxfF13K2btXwZ0otabTAuHSmKezLAlISQGbQtgG6JiPVhEuTMiYHVSQJTXS_QuCtSuZUpIUy9A9OyQzZAea91ZWnGDXph06TSAc2ix_Ynd19YM-ilWi81T0ZtZpBkfwhHkTq3rzXdX9Bz0qK3fjmnSi1pmbK9g-kYbRh6fG-gxYvk1uTO07asX1P2ik1tnSnTByrBNEju5qN8QWTcOLKSefww_-a-AQJxB7nEAyBP5QO8ZOoemwrkfecudvKD8HmwtXIJtO2z_nC5Maa_a8diSFUpWDHDZyEDT4ReqFL_4oJmSSSlzf3awzQnDI2P1_iKCc38ir323d5U6yZ08ujXDR390tlY1UNwHnJu6ofanNHcwR206u36vAv3rIXb41ra52Y24ZpZv_4iLA1vuOxRsjhoEarpVrctOcrDqGHR4GaW28VmfGXKwGikfmE54_63GPjrJz5iQwetF6AT6z7Xhs3sgrRvOJWOne7wyaWwuesBTgHxnniR2wvGCdvANX8u5p85E47hwGVRK1pQmg3HFtNXJ_gWUpzibtRM91wRpgRVCs07xnJ1N-sjH6Wq9mAqFQb9MX3dmAtT1HMWgej5UJHOxlRBwi2Ik2gr6EfArCAI5FkXjS2UwKI5RC-Njgij_ZwB_s_Wow4lfcXV6EOdNz_pGIW8QYvjt-0", + "d": "NVguzKdeM9LpLY1gutFswLWjvCFnozHNln8Xx2X4g-b9Hr6TSuyLRO_vYhufLGWyBScd67rOxMSgl1WjvVk4SCsPuwlfEVdLy6AR6uMB2TvBRjq3aiRoXhOM_tsT0GKZHZRiec3OL9rcPmueyVHRmeT4UymflGn9U56slSbeNCKjjjgNkdn9C_UhKiACi4R7A8NDIgdoJlQjOkhlyTtl3gaOyRJKEIXmKU7_KJ9QLiIrqeR_023RX4pNmHzhq-ln7J_M7DvNTldTapDjt6iCTA1RaUts7mFRjdwtyUZXQFjYQhh8FzEK2YQIvumk0TzKhqxvtGz-9Zu0UmVZrCWOmBBPOjKFvqylWL9eP5ln3xchI-V2D7eIOgQgrZxREe7gPGwbWjdt27wIGJMilEyFx4oNFNUSABz5zUADlNOPkIfQWARf54_uf1i_6K1WFNmwlZ_exqpUUQIjadciDPtfXBIsmKiB6rC86vLZL2oZBqSCSlRanOSQCyFoiF1AHu71oK1UPf1wvI75xXIjtfvNfckl0tUJFXQ3eEMUd-3Vdsqp4ZKM8Xy0Nku3S9eTEf4NuZyEwatQUMMtf6dXa8FVnHIWgKCeq6K1GMJqm2RCp9jlz1bp72UZR8XQh7jjpJSo2LFGeWGlhHhfE0MPgrof4XPSkm6aIb9V03kTwphb4Qk", + "p": "00RB7ab0zVMjcJqAOU1EiEDQs0ZsI4yLpfFfRoofpcxdkPxlCYbgQw-hLjG2wqSM_mEKc6jkXDwl-9eb7Cu0gGQLlYikgOi29Igq0Es_ExuurIpp3GjIymQoWiHrHJzdCIxXTV5simBEsrXKzlFXSLyha5Hx6ZLgdAq7ltQ2xACZEHbegvy3mjLY-EFsOc8SomA2cao3E3-MSKClR98m3pBmaNKqiVATh-o5QbGYmedxCCWXYNgfqArEOz2WP6kxcbLes5ZeR_lqv39AH-r4UAT9GXTS60cfk693qceLtnHDKSVGpWBB7oKjf9zlHwVP_yKnAtw_ngM2hs1SXmHxWw", + "q": "uKbgliLv09MKlsuCxXMqRIRvOzz74hFB2ZJ5GO_zJX4AVFNzIMmgUs1lY2UrRqu0nnuUUF4eJXCQz3yekkV5sUpYnHBhAy7PaqRSpCyeA6ME8w6yyO_lkVIU-r-MxYthkBct8Qro0vZRsWz9WjzMjqvKPgmcVxL5x3Bzohvzse17S3dGrMLdodffFs2WKszGOin3smLgoKekrE4H0yJAFJ2y7zNr8MpT3Yxia84dwPS3oPQmvCsDox950MA-ONOoefsBu0CqGXESB3odA4cb5D8Ev4tcD_LKKN7n3EYayMUlRyLo6vzJHSe-YG_edEkL1clNKD5GMdks4dFpkEy2Vw", + "dp": "ObPu_eYX_uyyRVMtqnj8OurZFd4qMxt0GQLwLugS9Mn41Fzbi9qW7joan-9cJ5WiHOiMXkLG_JpBFaQyJjNZvaeVsmX76GmnbuqyJuomdCWfc-jqORU1onHww414FfySMn-CaNNdef4JRXZ5yhhVarx6qlLWbCF9xLQZwHx96NLw-5o79DLqQueYc1YLJU71m_wDYtBG8sBnpH6cJiFBEJEIJ6FCivagHwNOWC8VqxOXv83kpsLhApjOOA_-Na0UoCVmxk5PugDZsywia5VV9SUnrAhIg5KNgVzvpiOmeVJhJ5_Sx-CeoXJ1DbtbqTlePCVO0G0vwwzFm9QAp17A-Q", + "dq": "TguamVqi63Ej5KCX4UTP9K3VJcCc5exNXNyf-n5Q0uPy17F907nk3Zqa4-v64p_oc6PgCCf_retqCYiurTxYNyspUNXe6y151aUmmOWS1A6vJNxtjKh463c6DtGvej9zOSfS7zKhPamG1esvGljgTN0nWhlNzy_iCv3oofgWhHnPxxHe-V5Ttvg6_rReaDCtCCB85RUNxFmXLIy-meq8Evqkip29XcTmtZmEb_Rqdwf4JTuMs7OqkePX5PecVQCBES2aib7HJoG4ERj38mnEzDAmnZ8VhlgkQioU7fFjmMBbBTooIEo_5ubJfJFMAPJu4il3Ry8rFB6q7bdJWwhReQ", + "qi": "ce5PKr8QwzpPtKsZJ5Oc8i2Vj9QZO49pdBIsQKmMGLgdLv1qIR9VlraQ37n_ovdfhEqgSACOEQASgmD2EqWIwsZFtPWnz2WaJRIu0weSfu5vYNrmTPpNSQZz1F0i_TfzH-9UP-8e_yPyWhHLleicIDmfveAt32yIKu8i4mwEU_Nm4jS1B1HyMd2kQVfQ5UHpaaEHRhUhFZHk0eBUjMFnaeNGZZgB0Y_n2u_s1AkPD5TsL74wQn3joq2w3toLJLJ7_JbvMlmbnakxFz6H09o6wMjj3H8QrYlJQwQzDUlESipq1YXlIoxl0m8KHRXnPzp3Zp7sPyV1jAni_PxXOHLpXg" +} \ No newline at end of file From df776d2fc5a438404d683659feb5ea0417225d0a Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 21 Jan 2025 11:26:18 -0500 Subject: [PATCH 12/51] finished --- .gitignore | 4 +- docker-compose.yml | 130 +++++++++++++--------------------------- orchestrator/src/app.ts | 122 ++++++++++++++++++++++++++++--------- requestwall.json | 11 ---- 4 files changed, 138 insertions(+), 129 deletions(-) delete mode 100644 requestwall.json diff --git a/.gitignore b/.gitignore index 8089ddd..ca600aa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,8 @@ venv/ .env/ .env env -wallet.json -wallet*.json +*wallet.json +*wallet*.json # Ignore distribution/build directories dist/ build/ diff --git a/docker-compose.yml b/docker-compose.yml index 46aa88f..155c4b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,186 +5,146 @@ services: postgres1: image: postgres:13 environment: - POSTGRES_USER: ${DB_USER_1:-myuser} - POSTGRES_PASSWORD: ${DB_PASSWORD_1:-mypassword} - POSTGRES_DB: ${DB_NAME_1:-mydatabase} + POSTGRES_USER: ${DB_USER_1:-myuser1} + POSTGRES_PASSWORD: ${DB_PASSWORD_1:-mypassword1} + POSTGRES_DB: ${DB_NAME_1:-mydatabase1} ports: - "5432:5432" networks: - - backend1 + - backend volumes: - pgdata1:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_1:-myuser} -d ${DB_NAME_1:-mydatabase}"] + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_1:-myuser1} -d ${DB_NAME_1:-mydatabase1}"] interval: 10s timeout: 5s retries: 5 orchestrator1: - image: randao/orchestrator:v0.2.10 + image: randao/orchestrator:v0.2.12 depends_on: postgres1: condition: service_healthy environment: DB_HOST: postgres1 DB_PORT: 5432 - DB_USER: ${DB_USER_1:-myuser} - DB_PASSWORD: ${DB_PASSWORD_1:-mypassword} - DB_NAME: ${DB_NAME_1:-mydatabase} + DB_USER: ${DB_USER_1:-myuser1} + DB_PASSWORD: ${DB_PASSWORD_1:-mypassword1} + DB_NAME: ${DB_NAME_1:-mydatabase1} ENVIRONMENT: local WALLET_JSON: ${WALLET_JSON_1} PROVIDER_ID: ${PROVIDER_ID_1} - DOCKER_NETWORK: backend1 + DOCKER_NETWORK: backend networks: - - backend1 + - backend volumes: - /var/run/docker.sock:/var/run/docker.sock - dbeaver1: - image: dbeaver/cloudbeaver:23.2.0 - environment: - CB_SERVER_SERVER_PORT: 8080 - CB_SERVER_ADMIN_NAME: "admin1" - CB_SERVER_ADMIN_PASSWORD: "admin123" - depends_on: - postgres1: - condition: service_healthy - networks: - - backend1 - ports: - - "8081:8978" - volumes: - - dbeaver-data1:/opt/cloudbeaver/workspace - # Instance 2 postgres2: image: postgres:13 environment: - POSTGRES_USER: ${DB_USER_2:-myuser} - POSTGRES_PASSWORD: ${DB_PASSWORD_2:-mypassword} - POSTGRES_DB: ${DB_NAME_2:-mydatabase} + POSTGRES_USER: ${DB_USER_2:-myuser2} + POSTGRES_PASSWORD: ${DB_PASSWORD_2:-mypassword2} + POSTGRES_DB: ${DB_NAME_2:-mydatabase2} ports: - "5433:5432" networks: - - backend2 + - backend volumes: - pgdata2:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_2:-myuser} -d ${DB_NAME_2:-mydatabase}"] + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_2:-myuser2} -d ${DB_NAME_2:-mydatabase2}"] interval: 10s timeout: 5s retries: 5 orchestrator2: - image: randao/orchestrator:v0.2.10 + image: randao/orchestrator:v0.2.12 depends_on: postgres2: condition: service_healthy environment: DB_HOST: postgres2 DB_PORT: 5432 - DB_USER: ${DB_USER_2:-myuser} - DB_PASSWORD: ${DB_PASSWORD_2:-mypassword} - DB_NAME: ${DB_NAME_2:-mydatabase} + DB_USER: ${DB_USER_2:-myuser2} + DB_PASSWORD: ${DB_PASSWORD_2:-mypassword2} + DB_NAME: ${DB_NAME_2:-mydatabase2} ENVIRONMENT: local WALLET_JSON: ${WALLET_JSON_2} PROVIDER_ID: ${PROVIDER_ID_2} - DOCKER_NETWORK: backend2 + DOCKER_NETWORK: backend networks: - - backend2 + - backend volumes: - /var/run/docker.sock:/var/run/docker.sock - dbeaver2: - image: dbeaver/cloudbeaver:23.2.0 - environment: - CB_SERVER_SERVER_PORT: 8080 - CB_SERVER_ADMIN_NAME: "admin2" - CB_SERVER_ADMIN_PASSWORD: "admin123" - depends_on: - postgres2: - condition: service_healthy - networks: - - backend2 - ports: - - "8082:8978" - volumes: - - dbeaver-data2:/opt/cloudbeaver/workspace - # Instance 3 postgres3: image: postgres:13 environment: - POSTGRES_USER: ${DB_USER_3:-myuser} - POSTGRES_PASSWORD: ${DB_PASSWORD_3:-mypassword} - POSTGRES_DB: ${DB_NAME_3:-mydatabase} + POSTGRES_USER: ${DB_USER_3:-myuser3} + POSTGRES_PASSWORD: ${DB_PASSWORD_3:-mypassword3} + POSTGRES_DB: ${DB_NAME_3:-mydatabase3} ports: - "5434:5432" networks: - - backend3 + - backend volumes: - pgdata3:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_3:-myuser} -d ${DB_NAME_3:-mydatabase}"] + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_3:-myuser3} -d ${DB_NAME_3:-mydatabase3}"] interval: 10s timeout: 5s retries: 5 orchestrator3: - image: randao/orchestrator:v0.2.10 + image: randao/orchestrator:v0.2.12 depends_on: postgres3: condition: service_healthy environment: DB_HOST: postgres3 DB_PORT: 5432 - DB_USER: ${DB_USER_3:-myuser} - DB_PASSWORD: ${DB_PASSWORD_3:-mypassword} - DB_NAME: ${DB_NAME_3:-mydatabase} + DB_USER: ${DB_USER_3:-myuser3} + DB_PASSWORD: ${DB_PASSWORD_3:-mypassword3} + DB_NAME: ${DB_NAME_3:-mydatabase3} ENVIRONMENT: local WALLET_JSON: ${WALLET_JSON_3} PROVIDER_ID: ${PROVIDER_ID_3} - DOCKER_NETWORK: backend3 + DOCKER_NETWORK: backend networks: - - backend3 + - backend volumes: - /var/run/docker.sock:/var/run/docker.sock - dbeaver3: + # Single DBeaver Instance + dbeaver: image: dbeaver/cloudbeaver:23.2.0 environment: CB_SERVER_SERVER_PORT: 8080 - CB_SERVER_ADMIN_NAME: "admin3" + CB_SERVER_ADMIN_NAME: "admin" CB_SERVER_ADMIN_PASSWORD: "admin123" - depends_on: - postgres3: - condition: service_healthy networks: - - backend3 + - backend ports: - - "8083:8978" + - "8080:8978" volumes: - - dbeaver-data3:/opt/cloudbeaver/workspace + - dbeaver-data:/opt/cloudbeaver/workspace requester: image: randao/requester:v0.1.20 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} - + requesterfast: image: randao/requester-fast:v0.1.0 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} networks: - backend1: - name: backend1 - driver: bridge - backend2: - name: backend2 - driver: bridge - backend3: - name: backend3 + backend: + name: backend driver: bridge volumes: @@ -194,9 +154,5 @@ volumes: driver: local pgdata3: driver: local - dbeaver-data1: - driver: local - dbeaver-data2: - driver: local - dbeaver-data3: + dbeaver-data: driver: local diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index fc1430e..1adfb7c 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -41,6 +41,8 @@ let ongoingRequest = false; let spotInterruptions = 0; let totalProvided = 0; let PreviousTotalAvailableRandom = 0; +const COMPLETION_RETENTION_PERIOD_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + // Retry logic for connecting to PostgreSQL async function connectWithRetry(): Promise { @@ -60,27 +62,28 @@ async function connectWithRetry(): Promise { // Setup the `verifiable_delay_functions` table if not exists async function setupDatabase(client: Client): Promise { - - // // Drop the table if it exists - // await client.query(` - // DROP TABLE IF EXISTS verifiable_delay_functions; - // `); - // console.log("'verifiable_delay_functions' table dropped."); - await client.query(` CREATE TABLE IF NOT EXISTS verifiable_delay_functions ( - id TEXT PRIMARY KEY, -- Define as TEXT to match UUID format + id TEXT PRIMARY KEY, request_id TEXT, modulus TEXT NOT NULL, input TEXT NOT NULL, output TEXT NOT NULL, proof JSON NOT NULL, - date TIMESTAMP DEFAULT CURRENT_TIMESTAMP + date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + detected_completed TIMESTAMP NULL ); `); + + // Ensure detected_completed column exists in case the table was created before it was added + await client.query(` + ALTER TABLE verifiable_delay_functions + ADD COLUMN IF NOT EXISTS detected_completed TIMESTAMP NULL; + `); console.log("Database setup complete. 'verifiable_delay_functions' table is ready."); } + // Modified function to trigger VDF job pod using ECS or Docker async function triggerVDFJobPod(): Promise { if (ENVIRONMENT === 'cloud') { @@ -387,20 +390,40 @@ async function polling(client: Client): Promise { console.log(`${logId} Starting Polling...`); try { + const startTime = Date.now(); // Start time of polling + console.log(`${logId} Step 1: Fetching open requests from the Randomness Client.`); + const step1Start = Date.now(); const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); + const step1End = Date.now(); console.log(`${logId} Step 1: Open Requests: ${JSON.stringify(openRequests)}`); console.log(openRequests); - console.log(`${logId} Step 1: Open requests fetched.`); + console.log(`${logId} Step 1: Open requests fetched. Time taken: ${(step1End - step1Start)}ms`); // Run Step 2, 3, and 4 concurrently after Step 1 + const step2Start = Date.now(); await Promise.all([ - processChallengeRequests(client, openRequests.activeChallengeRequests, logId), - processOutputRequests(client, openRequests.activeOutputRequests, logId), - cleanupFulfilledEntries(client, openRequests, logId) + (async () => { + const s2 = Date.now(); + await processChallengeRequests(client, openRequests.activeChallengeRequests, logId); + console.log(`${logId} Step 2 completed. Time taken: ${Date.now() - s2}ms`); + })(), + (async () => { + const s3 = Date.now(); + await processOutputRequests(client, openRequests.activeOutputRequests, logId); + console.log(`${logId} Step 3 completed. Time taken: ${Date.now() - s3}ms`); + })(), + (async () => { + const s4 = Date.now(); + await cleanupFulfilledEntries(client, openRequests, logId); + console.log(`${logId} Step 4 completed. Time taken: ${Date.now() - s4}ms`); + })(), ]); + const step2End = Date.now(); + + const totalTime = step2End - startTime; + console.log(`${logId} Polling cycle completed successfully. Total time taken: ${totalTime}ms`); - console.log(`${logId} Polling cycle completed successfully.`); } catch (error) { console.error(`${logId} An error occurred during polling:`, error); } finally { @@ -409,6 +432,7 @@ async function polling(client: Client): Promise { } + // Step 2: Process Challenge Requests (Database selection & assigning is atomic) async function processChallengeRequests( client: Client, @@ -537,30 +561,70 @@ async function cleanupFulfilledEntries( const logId = getLogId(); console.log(`${logId} Step 4: Checking for fulfilled entries no longer in use.`); - const noLongerUsedIds: string[] = []; - for (const ongoingId of ongoingFulfillments) { - const challengeInProgress = openRequests.activeChallengeRequests?.request_ids.includes(ongoingId); - const outputInProgress = openRequests.activeOutputRequests?.request_ids.includes(ongoingId); + const now = new Date(); + const cutoffTime = new Date(now.getTime() - COMPLETION_RETENTION_PERIOD_MS); + + try { + await client.query('BEGIN'); + + // Fetch all entries with a request_id + const result = await client.query(` + SELECT id, request_id, detected_completed + FROM verifiable_delay_functions + WHERE request_id IS NOT NULL + `); + + let markForDeletion: string[] = []; + let markAsCompleted: string[] = []; + + for (const row of result.rows) { + const { id, request_id, detected_completed } = row; + + // Check if this request is still active in challenge or output + const isStillInChallenge = openRequests.activeChallengeRequests?.request_ids.includes(request_id); + const isStillInOutput = openRequests.activeOutputRequests?.request_ids.includes(request_id); + + if (!isStillInChallenge && !isStillInOutput) { + if (!detected_completed) { + // Mark it for deletion by setting detected_completed timestamp + markAsCompleted.push(id); + } else if (new Date(detected_completed) < cutoffTime) { + // If already marked and older than retention period, delete it + markForDeletion.push(id); + } + } + } + + // Mark entries as completed + if (markAsCompleted.length > 0) { + await client.query(` + UPDATE verifiable_delay_functions + SET detected_completed = NOW() + WHERE id = ANY($1) + `, [markAsCompleted]); + console.log(`${logId} Marked ${markAsCompleted.length} entries as completed.`); + } - if (!challengeInProgress && !outputInProgress) { - noLongerUsedIds.push(ongoingId); + // Delete old completed entries + if (markForDeletion.length > 0) { + await client.query(` + DELETE FROM verifiable_delay_functions + WHERE id = ANY($1) + `, [markForDeletion]); + console.log(`${logId} Deleted ${markForDeletion.length} old completed entries.`); } - } - if (noLongerUsedIds.length > 0) { - console.log(`${logId} No longer in use: ${noLongerUsedIds.join(', ')}`); - noLongerUsedIds.forEach((id) => { - ongoingFulfillments.delete(id); - console.log(`${logId} Removed ID ${id} from ongoing fulfillments.`); - }); - } else { - console.log(`${logId} No fulfilled entries to remove.`); + await client.query('COMMIT'); + } catch (error) { + console.error(`${logId} Error in cleanupFulfilledEntries:`, error); + await client.query('ROLLBACK'); } console.log(`${logId} Step 4 completed.`); } + // Main function async function run(): Promise { const client = await connectWithRetry(); diff --git a/requestwall.json b/requestwall.json deleted file mode 100644 index ba122ef..0000000 --- a/requestwall.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "kty": "RSA", - "e": "AQAB", - "n": "mGLG_TCdxfF13K2btXwZ0otabTAuHSmKezLAlISQGbQtgG6JiPVhEuTMiYHVSQJTXS_QuCtSuZUpIUy9A9OyQzZAea91ZWnGDXph06TSAc2ix_Ynd19YM-ilWi81T0ZtZpBkfwhHkTq3rzXdX9Bz0qK3fjmnSi1pmbK9g-kYbRh6fG-gxYvk1uTO07asX1P2ik1tnSnTByrBNEju5qN8QWTcOLKSefww_-a-AQJxB7nEAyBP5QO8ZOoemwrkfecudvKD8HmwtXIJtO2z_nC5Maa_a8diSFUpWDHDZyEDT4ReqFL_4oJmSSSlzf3awzQnDI2P1_iKCc38ir323d5U6yZ08ujXDR390tlY1UNwHnJu6ofanNHcwR206u36vAv3rIXb41ra52Y24ZpZv_4iLA1vuOxRsjhoEarpVrctOcrDqGHR4GaW28VmfGXKwGikfmE54_63GPjrJz5iQwetF6AT6z7Xhs3sgrRvOJWOne7wyaWwuesBTgHxnniR2wvGCdvANX8u5p85E47hwGVRK1pQmg3HFtNXJ_gWUpzibtRM91wRpgRVCs07xnJ1N-sjH6Wq9mAqFQb9MX3dmAtT1HMWgej5UJHOxlRBwi2Ik2gr6EfArCAI5FkXjS2UwKI5RC-Njgij_ZwB_s_Wow4lfcXV6EOdNz_pGIW8QYvjt-0", - "d": "NVguzKdeM9LpLY1gutFswLWjvCFnozHNln8Xx2X4g-b9Hr6TSuyLRO_vYhufLGWyBScd67rOxMSgl1WjvVk4SCsPuwlfEVdLy6AR6uMB2TvBRjq3aiRoXhOM_tsT0GKZHZRiec3OL9rcPmueyVHRmeT4UymflGn9U56slSbeNCKjjjgNkdn9C_UhKiACi4R7A8NDIgdoJlQjOkhlyTtl3gaOyRJKEIXmKU7_KJ9QLiIrqeR_023RX4pNmHzhq-ln7J_M7DvNTldTapDjt6iCTA1RaUts7mFRjdwtyUZXQFjYQhh8FzEK2YQIvumk0TzKhqxvtGz-9Zu0UmVZrCWOmBBPOjKFvqylWL9eP5ln3xchI-V2D7eIOgQgrZxREe7gPGwbWjdt27wIGJMilEyFx4oNFNUSABz5zUADlNOPkIfQWARf54_uf1i_6K1WFNmwlZ_exqpUUQIjadciDPtfXBIsmKiB6rC86vLZL2oZBqSCSlRanOSQCyFoiF1AHu71oK1UPf1wvI75xXIjtfvNfckl0tUJFXQ3eEMUd-3Vdsqp4ZKM8Xy0Nku3S9eTEf4NuZyEwatQUMMtf6dXa8FVnHIWgKCeq6K1GMJqm2RCp9jlz1bp72UZR8XQh7jjpJSo2LFGeWGlhHhfE0MPgrof4XPSkm6aIb9V03kTwphb4Qk", - "p": "00RB7ab0zVMjcJqAOU1EiEDQs0ZsI4yLpfFfRoofpcxdkPxlCYbgQw-hLjG2wqSM_mEKc6jkXDwl-9eb7Cu0gGQLlYikgOi29Igq0Es_ExuurIpp3GjIymQoWiHrHJzdCIxXTV5simBEsrXKzlFXSLyha5Hx6ZLgdAq7ltQ2xACZEHbegvy3mjLY-EFsOc8SomA2cao3E3-MSKClR98m3pBmaNKqiVATh-o5QbGYmedxCCWXYNgfqArEOz2WP6kxcbLes5ZeR_lqv39AH-r4UAT9GXTS60cfk693qceLtnHDKSVGpWBB7oKjf9zlHwVP_yKnAtw_ngM2hs1SXmHxWw", - "q": "uKbgliLv09MKlsuCxXMqRIRvOzz74hFB2ZJ5GO_zJX4AVFNzIMmgUs1lY2UrRqu0nnuUUF4eJXCQz3yekkV5sUpYnHBhAy7PaqRSpCyeA6ME8w6yyO_lkVIU-r-MxYthkBct8Qro0vZRsWz9WjzMjqvKPgmcVxL5x3Bzohvzse17S3dGrMLdodffFs2WKszGOin3smLgoKekrE4H0yJAFJ2y7zNr8MpT3Yxia84dwPS3oPQmvCsDox950MA-ONOoefsBu0CqGXESB3odA4cb5D8Ev4tcD_LKKN7n3EYayMUlRyLo6vzJHSe-YG_edEkL1clNKD5GMdks4dFpkEy2Vw", - "dp": "ObPu_eYX_uyyRVMtqnj8OurZFd4qMxt0GQLwLugS9Mn41Fzbi9qW7joan-9cJ5WiHOiMXkLG_JpBFaQyJjNZvaeVsmX76GmnbuqyJuomdCWfc-jqORU1onHww414FfySMn-CaNNdef4JRXZ5yhhVarx6qlLWbCF9xLQZwHx96NLw-5o79DLqQueYc1YLJU71m_wDYtBG8sBnpH6cJiFBEJEIJ6FCivagHwNOWC8VqxOXv83kpsLhApjOOA_-Na0UoCVmxk5PugDZsywia5VV9SUnrAhIg5KNgVzvpiOmeVJhJ5_Sx-CeoXJ1DbtbqTlePCVO0G0vwwzFm9QAp17A-Q", - "dq": "TguamVqi63Ej5KCX4UTP9K3VJcCc5exNXNyf-n5Q0uPy17F907nk3Zqa4-v64p_oc6PgCCf_retqCYiurTxYNyspUNXe6y151aUmmOWS1A6vJNxtjKh463c6DtGvej9zOSfS7zKhPamG1esvGljgTN0nWhlNzy_iCv3oofgWhHnPxxHe-V5Ttvg6_rReaDCtCCB85RUNxFmXLIy-meq8Evqkip29XcTmtZmEb_Rqdwf4JTuMs7OqkePX5PecVQCBES2aib7HJoG4ERj38mnEzDAmnZ8VhlgkQioU7fFjmMBbBTooIEo_5ubJfJFMAPJu4il3Ry8rFB6q7bdJWwhReQ", - "qi": "ce5PKr8QwzpPtKsZJ5Oc8i2Vj9QZO49pdBIsQKmMGLgdLv1qIR9VlraQ37n_ovdfhEqgSACOEQASgmD2EqWIwsZFtPWnz2WaJRIu0weSfu5vYNrmTPpNSQZz1F0i_TfzH-9UP-8e_yPyWhHLleicIDmfveAt32yIKu8i4mwEU_Nm4jS1B1HyMd2kQVfQ5UHpaaEHRhUhFZHk0eBUjMFnaeNGZZgB0Y_n2u_s1AkPD5TsL74wQn3joq2w3toLJLJ7_JbvMlmbnakxFz6H09o6wMjj3H8QrYlJQwQzDUlESipq1YXlIoxl0m8KHRXnPzp3Zp7sPyV1jAni_PxXOHLpXg" -} \ No newline at end of file From f3d2f46b215a8d39a9c58e95ac1d6c6cd78dd900 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 29 Jan 2025 15:07:31 -0500 Subject: [PATCH 13/51] close --- terraform/{debug_task.tf => .debug_task.tf} | 72 ++++++++++----------- terraform/backend.tf | 0 terraform/package.json | 2 +- terraform/task_definitions.tf | 4 +- 4 files changed, 39 insertions(+), 39 deletions(-) rename terraform/{debug_task.tf => .debug_task.tf} (96%) delete mode 100644 terraform/backend.tf diff --git a/terraform/debug_task.tf b/terraform/.debug_task.tf similarity index 96% rename from terraform/debug_task.tf rename to terraform/.debug_task.tf index 9972781..492af95 100644 --- a/terraform/debug_task.tf +++ b/terraform/.debug_task.tf @@ -1,36 +1,36 @@ -# debug_task.tf - -resource "aws_ecs_task_definition" "debug_task" { - family = "debug-task" - network_mode = "awsvpc" - requires_compatibilities = ["FARGATE"] - cpu = "512" - memory = "1024" - - container_definitions = jsonencode([ - { - name = "debug-container" - image = "amazonlinux" - essential = true - command = ["/bin/sh", "-c", "while true; do sleep 60; done"] - environment = [ - { name = "DB_HOST", value = "postgres" }, - { name = "DB_PORT", value = "5432" }, - { name = "DB_USER", value = "myuser" }, - { name = "DB_PASSWORD", value = "mypassword" }, - { name = "DB_NAME", value = "mydatabase" } - ], - logConfiguration = { - logDriver = "awslogs" - options = { - awslogs-group = "/ecs/debug-task" - awslogs-region = var.aws_region - awslogs-stream-prefix = "debug-container" - } - } - } - ]) - - execution_role_arn = aws_iam_role.execution_role.arn - task_role_arn = aws_iam_role.task_role.arn -} +# debug_task.tf + +resource "aws_ecs_task_definition" "debug_task" { + family = "debug-task" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = "512" + memory = "1024" + + container_definitions = jsonencode([ + { + name = "debug-container" + image = "amazonlinux" + essential = true + command = ["/bin/sh", "-c", "while true; do sleep 60; done"] + environment = [ + { name = "DB_HOST", value = "postgres" }, + { name = "DB_PORT", value = "5432" }, + { name = "DB_USER", value = "myuser" }, + { name = "DB_PASSWORD", value = "mypassword" }, + { name = "DB_NAME", value = "mydatabase" } + ], + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = "/ecs/debug-task" + awslogs-region = var.aws_region + awslogs-stream-prefix = "debug-container" + } + } + } + ]) + + execution_role_arn = aws_iam_role.execution_role.arn + task_role_arn = aws_iam_role.task_role.arn +} diff --git a/terraform/backend.tf b/terraform/backend.tf deleted file mode 100644 index e69de29..0000000 diff --git a/terraform/package.json b/terraform/package.json index 9f6139e..b4f6756 100644 --- a/terraform/package.json +++ b/terraform/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "ao-process-clients": "^2.3.1" + "ao-process-clients": "^2.3.17" }, "devDependencies": { "@types/node": "^22.9.1" diff --git a/terraform/task_definitions.tf b/terraform/task_definitions.tf index 7755465..ac95bbb 100644 --- a/terraform/task_definitions.tf +++ b/terraform/task_definitions.tf @@ -9,7 +9,7 @@ resource "aws_ecs_task_definition" "orchestrator_service" { container_definitions = jsonencode([{ name = "orchestrator" - image = "randao/orchestrator:v0.1.4" + image = "randao/orchestrator:v0.2.0" essential = true environment = [ { name = "ENVIRONMENT", value = "cloud" }, @@ -47,7 +47,7 @@ resource "aws_ecs_task_definition" "vdf_job" { container_definitions = jsonencode([{ name = "vdf_job_container" - image = "randao/vdf_job:v0.1.0" + image = "randao/vdf_job:v0.1.4" essential = true command = ["python", "main.py"] # Set the default command to run environment = [ From e27d744fbfef85dbc0e9494f4e40bebcbc507653 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 30 Jan 2025 09:53:45 -0500 Subject: [PATCH 14/51] Finished --- docker-compose.yml | 12 +- docs/become-a-provider.md | 85 ++++++++ orchestrator/package.json | 2 +- orchestrator/src/app.ts | 58 ++---- orchestrator/src/ecs_config.ts | 59 ++++++ randwallet.json | 11 - requester/package.json | 2 +- requester/src/app.ts | 7 +- terraform/README.md | 72 +++++++ terraform/ecs.tf | 315 +++++++++++++++++++++++++++++ terraform/ecs_cluster.tf | 16 -- terraform/ecs_service.tf | 35 ---- terraform/iam_roles.tf | 13 +- terraform/locals.tf | 23 +++ terraform/networking.tf | 30 --- terraform/outputs.tf | 17 +- terraform/posgress.tf | 24 --- terraform/providers.tf | 12 +- terraform/rds.tf | 66 ++++++ terraform/secrets.tf | 53 +++++ terraform/security.tf | 33 +++ terraform/task_definitions.tf | 73 ------- terraform/terraform.tfvars | 34 ++++ terraform/terraform.tfvars.example | 24 +++ terraform/variables.tf | 50 ++++- terraform/vpc_endpoints.tf | 116 +++++++++++ 26 files changed, 986 insertions(+), 256 deletions(-) create mode 100644 orchestrator/src/ecs_config.ts delete mode 100644 randwallet.json create mode 100644 terraform/README.md create mode 100644 terraform/ecs.tf delete mode 100644 terraform/ecs_cluster.tf delete mode 100644 terraform/ecs_service.tf create mode 100644 terraform/locals.tf delete mode 100644 terraform/networking.tf delete mode 100644 terraform/posgress.tf create mode 100644 terraform/rds.tf create mode 100644 terraform/secrets.tf create mode 100644 terraform/security.tf delete mode 100644 terraform/task_definitions.tf create mode 100644 terraform/terraform.tfvars create mode 100644 terraform/terraform.tfvars.example create mode 100644 terraform/vpc_endpoints.tf diff --git a/docker-compose.yml b/docker-compose.yml index 155c4b1..42a2d02 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.2.12 + image: randao/orchestrator:v0.2.18 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.2.12 + image: randao/orchestrator:v0.2.18 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.2.12 + image: randao/orchestrator:v0.2.18 depends_on: postgres3: condition: service_healthy @@ -133,14 +133,10 @@ services: - dbeaver-data:/opt/cloudbeaver/workspace requester: - image: randao/requester:v0.1.20 + image: randao/requester:v0.2.0 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} - requesterfast: - image: randao/requester-fast:v0.1.0 - environment: - REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} networks: backend: diff --git a/docs/become-a-provider.md b/docs/become-a-provider.md index e69de29..c7f29ad 100644 --- a/docs/become-a-provider.md +++ b/docs/become-a-provider.md @@ -0,0 +1,85 @@ +# Node Provider Setup Guide + +Welcome to the Node Provider Setup Guide for our software. This document will help you deploy and maintain a node with guaranteed 100% uptime, ensuring optimal network performance and compliance. + +## Table of Contents +1. [Introduction](#introduction) +2. [Hardware Requirements](#hardware-requirements) +3. [Deployment Options](#deployment-options) + - [Option 1: AWS Deployment with Terraform](#option-1-aws-deployment-with-terraform) + - [Option 2: Virtual Machine Deployment with Docker Compose](#option-2-virtual-machine-deployment-with-docker-compose) +4. [Graceful Shutdown Policy](#graceful-shutdown-policy) + +--- + +## Introduction +As a node provider, you are responsible for ensuring 100% uptime. In the event of downtime, it is mandatory to run the graceful shutdown script to prevent being slashed. + +This guide will help you set up and manage your node efficiently. + +--- + +## Hardware Requirements +To run a node, the following hardware specifications are required: + +- **Minimum Hardware Requirements:** + - 4 GB memory + - 2 CPU cores +- **Recommended Deployment:** Access to an AWS account (we handle the configuration) +- **Note:** These requirements will increase over time to meet network demands. + +--- + +## Deployment Options + +### Option 1: AWS Deployment with Terraform +This is the recommended method, providing the best performance and uptime at the lowest cost. + +#### Steps to Deploy: +1. **Install Terraform:** + Follow the [official Terraform installation guide](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli). + +2. **Configure AWS Environment Variables:** + Open a terminal and enter the following commands: + ```bash + export AWS_ACCESS_KEY_ID="your-access-key-id" + export AWS_SECRET_ACCESS_KEY="your-secret-access-key" + export AWS_REGION="your-region" # e.g., us-east-1 + ``` + +3. **Initialize and Apply Terraform Configuration:** + Navigate to the Terraform directory of the project and run: + ```bash + terraform init + terraform apply + ``` + Type `yes` when prompted to confirm. + +This setup ensures your node is deployed with the highest uptime and optimal performance. + +### Option 2: Virtual Machine Deployment with Docker Compose +This option may cost more and depends on the uptime of the hardware you use. It is not recommended for long-term or high-performance node operators. + +#### Steps to Deploy: +1. **Install Docker Compose:** + Follow the [official Docker Compose installation guide](https://docs.docker.com/compose/install/). + +2. **Deploy Node:** + Navigate to the Docker Compose directory and run: + ```bash + docker-compose up -d + ``` + +This setup will work but may not guarantee the same performance or reliability as the AWS-based deployment. + +--- + +## Graceful Shutdown Policy +To avoid being slashed, it is critical to run the graceful shutdown script in the event of downtime. Failing to do so may result in penalties. + +Ensure that your monitoring and alert systems are set up to notify you immediately of any issues. + +--- + +By following this guide, you can successfully deploy and maintain a node with optimal uptime and performance. Thank you for contributing to the network's success! + diff --git a/orchestrator/package.json b/orchestrator/package.json index f5b241a..4238a12 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients":"3.5.15", + "ao-process-clients":"3.5.17", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 1adfb7c..7af74d3 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -5,7 +5,7 @@ import { getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomCl import { dbConfig } from './db_config.js'; const RANDOM_CONFIG: RandomClientConfig = { - tokenProcessId: "7enZBOhWsyU3A5oCt8HtMNNPHSxXYJVTlOGOetR9IDw", + tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", processId: "KbaY8P4h9wdHYKHlBSLbXN_yd-9gxUDxSgBackUxTiQ", wallet: JSON.parse(process.env.WALLET_JSON!), environment: 'mainnet' @@ -84,46 +84,25 @@ async function setupDatabase(client: Client): Promise { } +import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; + +// Cache for network configuration +let cachedNetworkConfig: NetworkConfig | null = null; + // Modified function to trigger VDF job pod using ECS or Docker async function triggerVDFJobPod(): Promise { if (ENVIRONMENT === 'cloud') { try { console.log("Cloud environment detected. Launching ECS task."); - const result = await ecs.runTask({ - cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', - taskDefinition: 'vdf-job', - capacityProviderStrategy: [ - { - capacityProvider: 'FARGATE_SPOT', - weight: 1 - } - ], - networkConfiguration: { - awsvpcConfiguration: { - subnets: [process.env.SUBNET_ID || 'subnet-12345678'], - securityGroups: [process.env.SECURITY_GROUP || 'sg-12345678'], - assignPublicIp: 'ENABLED' - } - }, - overrides: { - containerOverrides: [ - { - name: 'vdf_job_container', - environment: [ - { name: 'DATABASE_TYPE', value: 'postgresql' }, - { name: 'DATABASE_HOST', value: process.env.DB_HOST || 'cloud-postgres-host' }, - { name: 'DATABASE_PORT', value: process.env.DB_PORT || '5432' }, - { name: 'DATABASE_USER', value: process.env.DB_USER || 'myuser' }, - { name: 'DATABASE_PASSWORD', value: process.env.DB_PASSWORD || 'mypassword' }, - { name: 'DATABASE_NAME', value: process.env.DB_NAME || 'mydatabase' }, - ] - } - ] - }, - count: 1 - }).promise(); - - const taskArn = result.tasks?.[0]?.taskArn; + + // Get or refresh network configuration + if (!cachedNetworkConfig) { + console.log("Fetching network configuration..."); + cachedNetworkConfig = await getNetworkConfig(ecs); + console.log("Network config:", cachedNetworkConfig); + } + + const taskArn = await launchVDFTask(ecs, cachedNetworkConfig); if (taskArn) { ongoingTasks.add(taskArn); console.log(`ECS task started successfully: ${taskArn}`); @@ -136,6 +115,8 @@ async function triggerVDFJobPod(): Promise { console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); } else { console.error("Error launching ECS task:", error); + // Reset network config cache on error to force refresh on next attempt + cachedNetworkConfig = null; } return null; } @@ -269,6 +250,11 @@ async function checkAndFetchIfNeeded(client: Client): Promise { const res = await client.query('SELECT COUNT(*) AS count FROM verifiable_delay_functions WHERE request_id IS NULL'); const currentCount = parseInt(res.rows[0].count, 10); console.log("Total usable db entries: " + currentCount); + if(PreviousTotalAvailableRandom !=currentCount){ + console.log(`Updating avalible random values from ${PreviousTotalAvailableRandom} to ${currentCount}`) + randclient.updateProviderAvailableValues(currentCount) + PreviousTotalAvailableRandom = currentCount; + } if (currentCount < MINIMUM_ENTRIES) { const entriesNeeded = TARGET_ENTRIES - currentCount; diff --git a/orchestrator/src/ecs_config.ts b/orchestrator/src/ecs_config.ts new file mode 100644 index 0000000..18f30db --- /dev/null +++ b/orchestrator/src/ecs_config.ts @@ -0,0 +1,59 @@ +import AWS from 'aws-sdk'; + +export interface NetworkConfig { + subnets: string[]; + securityGroups: string[]; +} + +export async function getNetworkConfig(ecs: AWS.ECS): Promise { + // Get the task definition to extract network configuration + const taskDef = await ecs.describeTaskDefinition({ taskDefinition: 'vdf-job' }).promise(); + + // Get the service to extract network configuration + const services = await ecs.listServices({ cluster: process.env.ECS_CLUSTER_NAME }).promise(); + const serviceArn = services.serviceArns?.find(arn => arn.includes('vdf-job-service')); + + if (!serviceArn) { + throw new Error('VDF job service not found'); + } + + const service = await ecs.describeServices({ + cluster: process.env.ECS_CLUSTER_NAME, + services: [serviceArn] + }).promise(); + + const networkConfig = service.services?.[0]?.networkConfiguration?.awsvpcConfiguration; + + if (!networkConfig?.subnets || !networkConfig?.securityGroups) { + throw new Error('Network configuration not found'); + } + + return { + subnets: networkConfig.subnets, + securityGroups: networkConfig.securityGroups + }; +} + +export async function launchVDFTask(ecs: AWS.ECS, networkConfig: NetworkConfig): Promise { + const result = await ecs.runTask({ + cluster: process.env.ECS_CLUSTER_NAME, + taskDefinition: 'vdf-job', + capacityProviderStrategy: [ + { + capacityProvider: 'FARGATE_SPOT', + weight: 1 + } + ], + networkConfiguration: { + awsvpcConfiguration: { + subnets: networkConfig.subnets, + securityGroups: networkConfig.securityGroups, + assignPublicIp: 'ENABLED' + } + }, + count: 1 + }).promise(); + + const taskArn = result.tasks?.[0]?.taskArn; + return taskArn || null; +} diff --git a/randwallet.json b/randwallet.json deleted file mode 100644 index e68b8a3..0000000 --- a/randwallet.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "kty": "RSA", - "e": "AQAB", - "n": "yS6M-RRF4Q84Qhf8zr-DCvUMepQDmakGsj9tk1oOJpzWgL_jv3rWW8XtSZpPwQhN_tdiFqAiEgmdqiJxsqjGxez0o7WYL57P_usV-lRteuGPUQe0vaiWG2ls8-Gpi15oTLx-OSs4h8Ggbbd0X73fSK-xsZOgvfsGX7llGyZ0iTd6YQ84y2-cKBmPjdmaRxVhbtm8j4npsgCesmPDaKqJUTTiUhz5yXNfe-SPjELgRcPVGZ-sfgfw6PB5uduLy6iIeWLS_UOEYZRpi6F1wPkd4mJHX58GtJtY4gqJPaSueex-_ig1Q7UEFAjQEs-jaOaZOMZ-oBYhewa4DZwTPb1VcD5HzToPSgDuSZpBMVGYTRJ2-4scGWzk6ZWeUBpStiPe9-HMQgLo9PYh7JbptE6VP5B29YjRkCgzUk9cLCo7DdG6RJwkKlITeyOmjC1jrMecyhFhgahVYmKw8BtfkAHY2qJri06udSlVIIG0URJgecD3Ip9ug15cVQxLNCavnhlqPpeVoSoBDlguPzpLK8NoRB6LXf5C0kFDttARErJcVoRKWj_SlpfGDvbeuXrzQCeD4ijR4muJUzHmeveaNvy4BjPlwiJIMOMFJ1hy35P5QUaRxG37y46iBnYJyZtfs8xkmBboI6SNXxl8DMRlHkeitpRSF9o2JtF2N6zNG06se-M", - "d": "jseq5Vut_HyAwOelVFY2-p_Pq29A3t1HE1pQujM_t8e3tMh7KnpTh-6A6avKzoTGVgcmZkhf5c1NSGIJr3J5IB97wHQw_hsIIzNU9cTVrlBHFosRi0kKv4mi-bM-UNs_yrY8Hva9uBuDcFqzDQAEZ-HJiu3rNunhLC63wv0X2Aq3oArdlAQyH4OEjnfRNoHGFJYTbQvD_8DQ4GpNVQszSZ7uwLgvzvlC2vrrpfAQm3LQ2s6n7QpVv_xrZ6yZGoER2pR4iaZRXa-m-KIuoDYtL23wmbyTVbqq6kHwBP_LlFx0BI0kwFjH_DAE_FTA9MI0HEdDtvn96QJrvbz3Kuokp7JWWDbBFub8c3B6rn88KQRC3wOzAydB8ARcBbnPpkFKpw6fM-CCegc67qsJvwz2oBIcNZqqOD-X4wY7nc7vQ09i8zqD1oEkRdblnBCz4AgEh2ROBtm_kzAqvqDh92vnt0FphsD_BnEYyi_JJE92vscXWgseQj2CM4W5EL9EY1ijf0Nmkkb8DpCxfH6RwnI9BzNnitUvV16M1rxDZqga344vtnlEK7AvntkWRRFCqYE0EU0uBuWYBHKpbvGmnFovpDWXk6qIzSwci3SSMCk_i0GJ8IjJ-mO0x2wQQ7Pd0FkGOepzJq1DTlQakdVsX7LbiOUSs7Y1QTddG5G6PI5m_IE", - "p": "6BHvLYkIs0e6SPgTZwd8HBMChMauUsrro_rA1BtTZpij9a2ONK4Vji5C1_I-Ey_XEi889srF2-yOct8l2uBqZeDwgRf47Zxab3WCfwu2vnf4s7onwyI4UaFDSTCC7BZK11vg0P7vWDywnP4HorcqhAyiuY3If9bS-D_I_Ip8_ivP79qPu4H61Pe6G0zzPh1PiMoW3xdMeJaTU5ElMSiuFNhan_qeNc3EzXdo5udkL29j7pIczGspvoaT07wUd_RfqX3D_Fqdi23ZZRnclSz-BN9yj4J22Fe92IvOw0UM36alNkD-PRbw2c3zFDWgji445IxIDzG_8m6_BnIya2SG5Q", - "q": "3e0-wiXWs8kcf_gxPFFdgliBqa4PdajKZGg80J9MznErL6FJVrFvma4mDvhJdXyVzDyVIZIXH4oefR-RNz2nNycS8-icGQYap3KKZRNEhdD8HbCf3OL0nRffIRXRvfc0xgmLb0ysvXYpklrEPLMuqpvw3pxoSgW4_HZhQXQbkyX-xt9-u0v4DgAcxAiEwvCjee8Ul2iRxai01CesGTDMOivJU_ZpsjirVe94hIEwOW-2fjzX6zXiPJxkvtQbSwRMPzkuIYtXsj4kjf_0I0H6gqifFeVd-LHI6zWuYeoxrMWYFKBb6b3JQ_I6iS7vPgQ7z9obIE0Z7CjrUIysx_ZDJw", - "dp": "tMGzT_5aXnnR6SAAzNERpDRSU-UExsvzOmgHZa5bCaB-pM8n4nRtqa7ytYyjOQKcPDe6_mb2MdRRJ7wTmiYN-Yh5C7QGWdzcu9AFcrtG_ZgoiKTIb77pqvs2k31LnGPIq3GO7HqFJm8vCTj77YtJfEzzOh_rOVe0P1Q_UiT0Mm0hqyrLpTsaimLh_H21QH5IAr2VjvJwx8RQwFhfZajP3sCd5dmo_TNmxLrrZF56tE_IwHviHn6hpxrfbZ4jO0OGd1fUHWzfJUjMeWjpXPAMcvMwIgN2WhANeOt8gq_31QPRzy5UWHTT6HH3kZgrlFMAUVPKlLslTMlh1L2B9A_62Q", - "dq": "1ldqctda65_E7_AFla08NEVJTlm3wrr4Z0up2RDSfN0eic0r6RhMolBpn7G8OUXP5Edq_dZ8kNC0q8KOXZ0lYIZTrtGt2hlkKu_crMyUNO5oYkCR1iQ5f3Rr5CePwPr-tHrJegDDIeX7NsiFmd6xpsQgOtEzhLLMPMIVIsOCUney_98iJsGz3cnL_qX_m8wRCBaae7XafN55cCK0_Et-JHzf4UEwSpqjGMfGTav8qKy1xGz9WcZcMJAYWZrAlY6cGcAfRvSvCY8tfRyFbnwt-H3l0J8MSMNlO49IUnd_7M-XF-zdeP79YauVT6POG8a5AgI0itkMvWO0CsMjqam2pw", - "qi": "xMOtYWsparV6PZR0FqEzafuVpZLCE3Y8NR81f1ZBFUNhZVd4nGSlC5dgozfWJ--Gh9ejyb8-vvJaK2FJCUqODQ-xWQm48jADCOSOmYaM7IR7oqLJGdFWbac-IJm68_53Ql_F6OSrzZtjaphQ0dR5ax4NibE5bTx70eiAnlgdNiijZBmGlfVpMQopb3M3rOSZDtw_WBsSiduIrPIjy2FcMORpOk3849KOHb1nxCWuqWfxtywzxptRuj_RKIHmgqQ2owvJpDCyHbQrgVgqZhT7JnPAcUA7U8hMSl9I-tDlk9YhakZsMtEzQQgOERb7p7_N3d10OE-2VitpH4_Gl_u0Uw" -} \ No newline at end of file diff --git a/requester/package.json b/requester/package.json index eb0bca0..798686b 100644 --- a/requester/package.json +++ b/requester/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "3.5.15", + "ao-process-clients": "3.5.17", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/requester/src/app.ts b/requester/src/app.ts index 8f4dec8..99b2bd6 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -8,14 +8,15 @@ import { const PROVIDER_IDS = [ "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE", "c8Iq4yunDnsJWGSz_wYwQU--O9qeODKHiRdUkQkW2p8", - "Sr3HVH0Nh6iZzbORLpoQFOEvmsuKjXsHswSWH760KAk" + "Sr3HVH0Nh6iZzbORLpoQFOEvmsuKjXsHswSWH760KAk", + "1zlA7nKecUGevGNAEbjim_SlbioOI6daNNn2luDEHb0" ]; -const RETRY_DELAY_MS = 600; //1 minute +const RETRY_DELAY_MS = 5000; //5 seconds const CHANCE_TO_CALL_RANDOM = 1; const RANDOM_CONFIG: RandomClientConfig = { - tokenProcessId: "7enZBOhWsyU3A5oCt8HtMNNPHSxXYJVTlOGOetR9IDw", + tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", processId: "KbaY8P4h9wdHYKHlBSLbXN_yd-9gxUDxSgBackUxTiQ", wallet: JSON.parse(process.env.REQUEST_WALLET_JSON!), environment: "mainnet", diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..4ba189c --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,72 @@ +# Terraform Configuration for Randomness Provider + +This Terraform configuration sets up: +- One orchestrator service on ECS Fargate +- One PostgreSQL RDS instance +- VDF Fargate spot job configuration +- AWS Secrets Manager for sensitive data + +## Setup Instructions + +1. Copy the example variables file: +```bash +cp terraform.tfvars.example terraform.tfvars +``` + +2. Edit `terraform.tfvars` with your configuration: + - Set your database credentials + - Set your provider ID + - Copy your wallet JSON and paste it into the `local_wallet_json` variable + +Example wallet JSON format: +```json +{ + "address": "your-wallet-address", + "privateKey": "your-private-key" +} +``` + +Note: The wallet JSON should be pasted directly into the terraform.tfvars file using the heredoc syntax (< Date: Thu, 30 Jan 2025 11:44:48 -0500 Subject: [PATCH 15/51] Finished --- README.md | 65 ++++ .../2docker-compose.yml | 0 docker-compose/README.md | 15 + .../docker-compose.yml | 308 +++++++++--------- docs/become-a-provider.md | 85 ----- terraform/README.md | 121 +++++++ 6 files changed, 355 insertions(+), 239 deletions(-) rename 2docker-compose.yml => docker-compose/2docker-compose.yml (100%) create mode 100644 docker-compose/README.md rename docker-compose.yml => docker-compose/docker-compose.yml (96%) delete mode 100644 docs/become-a-provider.md diff --git a/README.md b/README.md index e69de29..e0e989d 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,65 @@ +# Node Provider Setup Guide + +Welcome to the Node Provider Setup Guide for our software. This document will help you deploy and maintain a node with guaranteed 100% uptime, ensuring optimal network performance and compliance. + +## Table of Contents +1. [Introduction](#introduction) +2. [Hardware Requirements](#hardware-requirements) +3. [Deployment Options](#deployment-options) + - [Option 1: AWS Deployment with Terraform](#option-1-aws-deployment-with-terraform) + - [Option 2: Virtual Machine Deployment with Docker Compose](#option-2-virtual-machine-deployment-with-docker-compose) +4. [Graceful Shutdown Policy](#graceful-shutdown-policy) + +--- + +## Introduction +As a node provider, you are responsible for ensuring 100% uptime. In the event of downtime, it is mandatory to run the graceful shutdown script to prevent being slashed. + +This guide will help you set up and manage your node efficiently. + +--- + +## Hardware Requirements +To run a node, the following hardware specifications are required: + +- **Minimum Hardware Requirements:** + - 4 GB memory + - 2 CPU cores +- **Recommended Deployment:** Access to an AWS account (we handle the configuration) +- **Note:** These requirements will increase over time to meet network demands. + +--- + +## Deployment Options + +### Option 1: AWS Deployment with Terraform +This is the recommended method, providing the best performance and uptime at the lowest cost. +[TerraForm setup](./terraform/README.md) + + + +### Option 2: Virtual Machine Deployment with Docker Compose +This option may cost more and depends on the uptime of the hardware you use. It is not recommended for long-term or high-performance node operators. +[Docker-compose setup](./docker-compose/README.md) + + +## Graceful Shutdown Policy +To avoid being slashed, it is critical to run the graceful shutdown script in the event of downtime. Failing to do so may result in penalties. + +Ensure that your monitoring and alert systems are set up to notify you immediately of any issues. + +--- + +By following this guide, you can successfully deploy and maintain a node with optimal uptime and performance. Thank you for contributing to the network's success! + +Staking + +After successfully setting up your node, you will need to provide proof that the gateway is operational. + +Show logs from the node setup to Ethan for verification. + +Upon confirmation, Ethan will provide your provider address with the necessary funds. + +3.Navigate to [Insert link here] to stake your funds and configure your provider information. + +By completing this process, you will fully activate your node and ensure it is ready for network participation. \ No newline at end of file diff --git a/2docker-compose.yml b/docker-compose/2docker-compose.yml similarity index 100% rename from 2docker-compose.yml rename to docker-compose/2docker-compose.yml diff --git a/docker-compose/README.md b/docker-compose/README.md new file mode 100644 index 0000000..0f63a1b --- /dev/null +++ b/docker-compose/README.md @@ -0,0 +1,15 @@ +#### Steps to Deploy: +1. **Install Docker Compose:** + Follow the [official Docker Compose installation guide](https://docs.docker.com/compose/install/). + +2. **Deploy Node:** + Navigate to the Docker Compose directory and run: + ```bash + docker-compose up -d + ``` + +This setup will work but may not guarantee the same performance or reliability as the AWS-based deployment. + +--- + +[Main docs](../README.md) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose/docker-compose.yml similarity index 96% rename from docker-compose.yml rename to docker-compose/docker-compose.yml index 42a2d02..32100cb 100644 --- a/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -1,154 +1,154 @@ -version: '3.8' - -services: - # Instance 1 - postgres1: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_1:-myuser1} - POSTGRES_PASSWORD: ${DB_PASSWORD_1:-mypassword1} - POSTGRES_DB: ${DB_NAME_1:-mydatabase1} - ports: - - "5432:5432" - networks: - - backend - volumes: - - pgdata1:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_1:-myuser1} -d ${DB_NAME_1:-mydatabase1}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator1: - image: randao/orchestrator:v0.2.18 - depends_on: - postgres1: - condition: service_healthy - environment: - DB_HOST: postgres1 - DB_PORT: 5432 - DB_USER: ${DB_USER_1:-myuser1} - DB_PASSWORD: ${DB_PASSWORD_1:-mypassword1} - DB_NAME: ${DB_NAME_1:-mydatabase1} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_1} - PROVIDER_ID: ${PROVIDER_ID_1} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 2 - postgres2: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_2:-myuser2} - POSTGRES_PASSWORD: ${DB_PASSWORD_2:-mypassword2} - POSTGRES_DB: ${DB_NAME_2:-mydatabase2} - ports: - - "5433:5432" - networks: - - backend - volumes: - - pgdata2:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_2:-myuser2} -d ${DB_NAME_2:-mydatabase2}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator2: - image: randao/orchestrator:v0.2.18 - depends_on: - postgres2: - condition: service_healthy - environment: - DB_HOST: postgres2 - DB_PORT: 5432 - DB_USER: ${DB_USER_2:-myuser2} - DB_PASSWORD: ${DB_PASSWORD_2:-mypassword2} - DB_NAME: ${DB_NAME_2:-mydatabase2} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_2} - PROVIDER_ID: ${PROVIDER_ID_2} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 3 - postgres3: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_3:-myuser3} - POSTGRES_PASSWORD: ${DB_PASSWORD_3:-mypassword3} - POSTGRES_DB: ${DB_NAME_3:-mydatabase3} - ports: - - "5434:5432" - networks: - - backend - volumes: - - pgdata3:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_3:-myuser3} -d ${DB_NAME_3:-mydatabase3}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator3: - image: randao/orchestrator:v0.2.18 - depends_on: - postgres3: - condition: service_healthy - environment: - DB_HOST: postgres3 - DB_PORT: 5432 - DB_USER: ${DB_USER_3:-myuser3} - DB_PASSWORD: ${DB_PASSWORD_3:-mypassword3} - DB_NAME: ${DB_NAME_3:-mydatabase3} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_3} - PROVIDER_ID: ${PROVIDER_ID_3} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Single DBeaver Instance - dbeaver: - image: dbeaver/cloudbeaver:23.2.0 - environment: - CB_SERVER_SERVER_PORT: 8080 - CB_SERVER_ADMIN_NAME: "admin" - CB_SERVER_ADMIN_PASSWORD: "admin123" - networks: - - backend - ports: - - "8080:8978" - volumes: - - dbeaver-data:/opt/cloudbeaver/workspace - - requester: - image: randao/requester:v0.2.0 - environment: - REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} - - -networks: - backend: - name: backend - driver: bridge - -volumes: - pgdata1: - driver: local - pgdata2: - driver: local - pgdata3: - driver: local - dbeaver-data: - driver: local +version: '3.8' + +services: + # Instance 1 + postgres1: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_1:-myuser1} + POSTGRES_PASSWORD: ${DB_PASSWORD_1:-mypassword1} + POSTGRES_DB: ${DB_NAME_1:-mydatabase1} + ports: + - "5432:5432" + networks: + - backend + volumes: + - pgdata1:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_1:-myuser1} -d ${DB_NAME_1:-mydatabase1}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator1: + image: randao/orchestrator:v0.2.18 + depends_on: + postgres1: + condition: service_healthy + environment: + DB_HOST: postgres1 + DB_PORT: 5432 + DB_USER: ${DB_USER_1:-myuser1} + DB_PASSWORD: ${DB_PASSWORD_1:-mypassword1} + DB_NAME: ${DB_NAME_1:-mydatabase1} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_1} + PROVIDER_ID: ${PROVIDER_ID_1} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 2 + postgres2: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_2:-myuser2} + POSTGRES_PASSWORD: ${DB_PASSWORD_2:-mypassword2} + POSTGRES_DB: ${DB_NAME_2:-mydatabase2} + ports: + - "5433:5432" + networks: + - backend + volumes: + - pgdata2:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_2:-myuser2} -d ${DB_NAME_2:-mydatabase2}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator2: + image: randao/orchestrator:v0.2.18 + depends_on: + postgres2: + condition: service_healthy + environment: + DB_HOST: postgres2 + DB_PORT: 5432 + DB_USER: ${DB_USER_2:-myuser2} + DB_PASSWORD: ${DB_PASSWORD_2:-mypassword2} + DB_NAME: ${DB_NAME_2:-mydatabase2} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_2} + PROVIDER_ID: ${PROVIDER_ID_2} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 3 + postgres3: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_3:-myuser3} + POSTGRES_PASSWORD: ${DB_PASSWORD_3:-mypassword3} + POSTGRES_DB: ${DB_NAME_3:-mydatabase3} + ports: + - "5434:5432" + networks: + - backend + volumes: + - pgdata3:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_3:-myuser3} -d ${DB_NAME_3:-mydatabase3}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator3: + image: randao/orchestrator:v0.2.18 + depends_on: + postgres3: + condition: service_healthy + environment: + DB_HOST: postgres3 + DB_PORT: 5432 + DB_USER: ${DB_USER_3:-myuser3} + DB_PASSWORD: ${DB_PASSWORD_3:-mypassword3} + DB_NAME: ${DB_NAME_3:-mydatabase3} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_3} + PROVIDER_ID: ${PROVIDER_ID_3} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Single DBeaver Instance + dbeaver: + image: dbeaver/cloudbeaver:23.2.0 + environment: + CB_SERVER_SERVER_PORT: 8080 + CB_SERVER_ADMIN_NAME: "admin" + CB_SERVER_ADMIN_PASSWORD: "admin123" + networks: + - backend + ports: + - "8080:8978" + volumes: + - dbeaver-data:/opt/cloudbeaver/workspace + + requester: + image: randao/requester:v0.2.0 + environment: + REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} + + +networks: + backend: + name: backend + driver: bridge + +volumes: + pgdata1: + driver: local + pgdata2: + driver: local + pgdata3: + driver: local + dbeaver-data: + driver: local diff --git a/docs/become-a-provider.md b/docs/become-a-provider.md deleted file mode 100644 index c7f29ad..0000000 --- a/docs/become-a-provider.md +++ /dev/null @@ -1,85 +0,0 @@ -# Node Provider Setup Guide - -Welcome to the Node Provider Setup Guide for our software. This document will help you deploy and maintain a node with guaranteed 100% uptime, ensuring optimal network performance and compliance. - -## Table of Contents -1. [Introduction](#introduction) -2. [Hardware Requirements](#hardware-requirements) -3. [Deployment Options](#deployment-options) - - [Option 1: AWS Deployment with Terraform](#option-1-aws-deployment-with-terraform) - - [Option 2: Virtual Machine Deployment with Docker Compose](#option-2-virtual-machine-deployment-with-docker-compose) -4. [Graceful Shutdown Policy](#graceful-shutdown-policy) - ---- - -## Introduction -As a node provider, you are responsible for ensuring 100% uptime. In the event of downtime, it is mandatory to run the graceful shutdown script to prevent being slashed. - -This guide will help you set up and manage your node efficiently. - ---- - -## Hardware Requirements -To run a node, the following hardware specifications are required: - -- **Minimum Hardware Requirements:** - - 4 GB memory - - 2 CPU cores -- **Recommended Deployment:** Access to an AWS account (we handle the configuration) -- **Note:** These requirements will increase over time to meet network demands. - ---- - -## Deployment Options - -### Option 1: AWS Deployment with Terraform -This is the recommended method, providing the best performance and uptime at the lowest cost. - -#### Steps to Deploy: -1. **Install Terraform:** - Follow the [official Terraform installation guide](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli). - -2. **Configure AWS Environment Variables:** - Open a terminal and enter the following commands: - ```bash - export AWS_ACCESS_KEY_ID="your-access-key-id" - export AWS_SECRET_ACCESS_KEY="your-secret-access-key" - export AWS_REGION="your-region" # e.g., us-east-1 - ``` - -3. **Initialize and Apply Terraform Configuration:** - Navigate to the Terraform directory of the project and run: - ```bash - terraform init - terraform apply - ``` - Type `yes` when prompted to confirm. - -This setup ensures your node is deployed with the highest uptime and optimal performance. - -### Option 2: Virtual Machine Deployment with Docker Compose -This option may cost more and depends on the uptime of the hardware you use. It is not recommended for long-term or high-performance node operators. - -#### Steps to Deploy: -1. **Install Docker Compose:** - Follow the [official Docker Compose installation guide](https://docs.docker.com/compose/install/). - -2. **Deploy Node:** - Navigate to the Docker Compose directory and run: - ```bash - docker-compose up -d - ``` - -This setup will work but may not guarantee the same performance or reliability as the AWS-based deployment. - ---- - -## Graceful Shutdown Policy -To avoid being slashed, it is critical to run the graceful shutdown script in the event of downtime. Failing to do so may result in penalties. - -Ensure that your monitoring and alert systems are set up to notify you immediately of any issues. - ---- - -By following this guide, you can successfully deploy and maintain a node with optimal uptime and performance. Thank you for contributing to the network's success! - diff --git a/terraform/README.md b/terraform/README.md index 4ba189c..b999b0d 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -1,3 +1,121 @@ +#### Steps to Deploy: +1. **Install Terraform:** + Follow the [official Terraform installation guide](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli). + +2. **Log into AWS and set up IAM user and Policy** + Go To IAM and create a new policy called RandAO-Provider-Admin + Paste this in the JSON + ```{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecs:CreateCluster", + "ecs:DeleteCluster", + "ecs:CreateService", + "ecs:DeleteService", + "ecs:UpdateService", + "ecs:RegisterTaskDefinition", + "ecs:DeregisterTaskDefinition", + "ecs:ListTaskDefinitions", + "ecs:DescribeTaskDefinition", + "ecs:PutClusterCapacityProviders" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:CreateRole", + "iam:DeleteRole", + "iam:GetRole", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy", + "iam:PassRole" + ], + "Resource": "arn:aws:iam::*:role/orchestrator-*" + }, + { + "Effect": "Allow", + "Action": [ + "secretsmanager:CreateSecret", + "secretsmanager:DeleteSecret", + "secretsmanager:GetSecretValue", + "secretsmanager:PutSecretValue", + "secretsmanager:UpdateSecret", + "secretsmanager:TagResource" + ], + "Resource": "arn:aws:secretsmanager:*:*:secret:/orchestrator/*" + }, + { + "Effect": "Allow", + "Action": [ + "rds:CreateDBInstance", + "rds:DeleteDBInstance", + "rds:ModifyDBInstance", + "rds:DescribeDBInstances", + "rds:CreateDBSubnetGroup", + "rds:DeleteDBSubnetGroup", + "rds:ModifyDBSubnetGroup" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:DeleteLogGroup", + "logs:PutRetentionPolicy" + ], + "Resource": "arn:aws:logs:*:*:log-group:/ecs/*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateSecurityGroup", + "ec2:DeleteSecurityGroup", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:CreateVpcEndpoint", + "ec2:DeleteVpcEndpoints", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeSecurityGroups", + "ec2:DescribeNetworkInterfaces" + ], + "Resource": "*" + } + ] +}``` + Save the JSON and name it + Click on Users and create a new user called TeraformDeployer + Attach this new policy directly + Save the User + Click on the user and go to the Security Credentials tab + Create an access key and choose CLI + Save the variables for the next step + +3. **Configure AWS Environment Variables:** + Open a terminal and enter the following commands: + ```bash + export AWS_ACCESS_KEY_ID="your-access-key-id" + export AWS_SECRET_ACCESS_KEY="your-secret-access-key" + export AWS_REGION="your-region" # e.g., us-east-1 + ``` + +3. **Initialize and Apply Terraform Configuration:** + Navigate to the Terraform directory of the project and run: + ```bash + terraform init + terraform apply + ``` + Type `yes` when prompted to confirm. + +This setup ensures your node is deployed with the highest uptime and optimal performance. + + # Terraform Configuration for Randomness Provider This Terraform configuration sets up: @@ -70,3 +188,6 @@ Optional variables (with defaults): - `aws_region`: AWS region (default: us-east-1) - `db_name`: Database name (default: orchestrator_db) - `secrets_prefix`: Prefix for secrets (default: /orchestrator) + + +[Main docs](../README.md) \ No newline at end of file From 339f0eadf43745e8f84c5253d25254201c3fc87c Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 30 Jan 2025 11:49:46 -0500 Subject: [PATCH 16/51] Finished --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e0e989d..f377ac3 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,14 @@ To run a node, the following hardware specifications are required: ### Option 1: AWS Deployment with Terraform This is the recommended method, providing the best performance and uptime at the lowest cost. + [TerraForm setup](./terraform/README.md) ### Option 2: Virtual Machine Deployment with Docker Compose This option may cost more and depends on the uptime of the hardware you use. It is not recommended for long-term or high-performance node operators. + [Docker-compose setup](./docker-compose/README.md) From a2ec06b1e7f36e2a8fcc27a5eb6beecfdb866f41 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 30 Jan 2025 11:51:34 -0500 Subject: [PATCH 17/51] Finished --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f377ac3..9e149cc 100644 --- a/README.md +++ b/README.md @@ -50,18 +50,20 @@ To avoid being slashed, it is critical to run the graceful shutdown script in th Ensure that your monitoring and alert systems are set up to notify you immediately of any issues. +TODO + --- -By following this guide, you can successfully deploy and maintain a node with optimal uptime and performance. Thank you for contributing to the network's success! -Staking +## Staking After successfully setting up your node, you will need to provide proof that the gateway is operational. -Show logs from the node setup to Ethan for verification. +1. Show logs from the node setup to Ethan for verification. -Upon confirmation, Ethan will provide your provider address with the necessary funds. +2. Upon confirmation, Ethan will provide your provider address with the necessary funds. -3.Navigate to [Insert link here] to stake your funds and configure your provider information. +3. Navigate to [Insert link here] to stake your funds and configure your provider information. -By completing this process, you will fully activate your node and ensure it is ready for network participation. \ No newline at end of file +By completing this process, you will fully activate your node and ensure it is ready for network participation. +Thank you for contributing to the network's success! \ No newline at end of file From f61d89aa607dd6b83df0e306850f24462a515b00 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 30 Jan 2025 14:11:53 -0500 Subject: [PATCH 18/51] Finished --- README.md | 26 ++++++++++++++++++------ docker-compose/.env.example | 16 +++++++++++++++ docker-compose/2docker-compose.yml | 32 +----------------------------- docker-compose/README.md | 2 +- 4 files changed, 38 insertions(+), 38 deletions(-) create mode 100644 docker-compose/.env.example diff --git a/README.md b/README.md index 9e149cc..688ee9b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Node Provider Setup Guide -Welcome to the Node Provider Setup Guide for our software. This document will help you deploy and maintain a node with guaranteed 100% uptime, ensuring optimal network performance and compliance. +This guide will walk you through getting your randomness provider set up and connected to the network so you can start contributing to the protocal and participating in decentralized randomness! ## Table of Contents 1. [Introduction](#introduction) @@ -15,7 +15,14 @@ Welcome to the Node Provider Setup Guide for our software. This document will he ## Introduction As a node provider, you are responsible for ensuring 100% uptime. In the event of downtime, it is mandatory to run the graceful shutdown script to prevent being slashed. -This guide will help you set up and manage your node efficiently. +Your provider does 3 main things. +1. It updates the amount of avalible random it has stored on chain. Each random takes a fair biot of compute to create so that it can be compliant to our commit reveal time delay scheme. +2. It detects someone has requested random from you and it provides the input number to your time delay function. This is not the final random number. +3. It detects all parties have submited their input number and then it provides the output number as well as the proof of history checkpoints to the chain to be verified. This output is the random number that will be used on chain. + +It will do these three steps as fast as it can inorder to get the complete random on chain as quickly as possible. Faster providers will be incentivised for their speed and slower ones will been penalized. If a provider is too slow for step 2 it will be slashed a small amount. If a provider is too slow for step 3 they will be considered malicious and slashed heavily. + +In the event you need to take your provider offline you must run the gracefull shutdown which will run step 1 once with a value of -1 indicating you are no longer offering random. After that your node will finish all requests in step 2 and 3 then turn off. --- @@ -30,6 +37,12 @@ To run a node, the following hardware specifications are required: --- + +## What the hardware runs +The hardware you stand up runs 3 services. It stands up a provider. A database for the provider and it spins up temporary jobs to generate random and store it in the database. +In AWS the cheapest and highest performance solution is used for each of these. when running with docekr compose your machinbe will run each of these services itself in a containerized environment. Quick and scalable but not as cheap or efficient as the AWS solution. + + ## Deployment Options ### Option 1: AWS Deployment with Terraform @@ -38,7 +51,6 @@ This is the recommended method, providing the best performance and uptime at the [TerraForm setup](./terraform/README.md) - ### Option 2: Virtual Machine Deployment with Docker Compose This option may cost more and depends on the uptime of the hardware you use. It is not recommended for long-term or high-performance node operators. @@ -46,11 +58,13 @@ This option may cost more and depends on the uptime of the hardware you use. It ## Graceful Shutdown Policy -To avoid being slashed, it is critical to run the graceful shutdown script in the event of downtime. Failing to do so may result in penalties. +To avoid being slashed, it is critical to run the graceful shutdown in the event of downtime. Failing to do so may result in penalties. -Ensure that your monitoring and alert systems are set up to notify you immediately of any issues. +To run this go to "TODO" and navigate to your node and select the "SHUT DOWN" button and sign the transaction. +This will tell your provider to stop serving random. -TODO +After the maintinance is done and your provider is back up again click "START UP" button. +This will tell your provider to start serving random. --- diff --git a/docker-compose/.env.example b/docker-compose/.env.example new file mode 100644 index 0000000..80d1ef5 --- /dev/null +++ b/docker-compose/.env.example @@ -0,0 +1,16 @@ +DB_USER=myuser +DB_PASSWORD=mypassword +DB_NAME=mydatabase +DOCKER_NETWORK=backend +WALLET_JSON = '{ + "kty": "RSA", + "e": "test", + "n": "test", + "d": "test", + "p": "test", + "q": "test", + "dp": "test", + "dq": "test", + "qi": "test" +}' +PROVIDER_ID = "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE" diff --git a/docker-compose/2docker-compose.yml b/docker-compose/2docker-compose.yml index df93961..d48a879 100644 --- a/docker-compose/2docker-compose.yml +++ b/docker-compose/2docker-compose.yml @@ -20,7 +20,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.1.78 + image: randao/orchestrator:v0.2.18 depends_on: postgres: condition: service_healthy @@ -41,34 +41,6 @@ services: - /var/run/docker.sock:/var/run/docker.sock # Mount Docker socket - ./wallet.json:/app/wallet.json # Mount wallet.json into the container - requester: - image: randao/requester:v0.1.13 - environment: - PATH_TO_WALLET: /app/wallet.json # Path inside the container - REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} - PROVIDER_ID: ${PROVIDER_ID} - DOCKER_NETWORK: backend # Passing the network name - networks: - - backend - volumes: - - ./wallet.json:/app/wallet.json # Mount wallet.json into the container - - dbeaver: - image: dbeaver/cloudbeaver:23.2.0 - environment: - CB_SERVER_SERVER_PORT: 8080 - CB_SERVER_ADMIN_NAME: "admin" - CB_SERVER_ADMIN_PASSWORD: "admin123" - depends_on: - postgres: - condition: service_healthy - networks: - - backend - ports: - - "8081:8978" # Expose the DBeaver web UI - volumes: - - dbeaver-data:/opt/cloudbeaver/workspace - networks: backend: name: backend # This will set the network name explicitly @@ -77,5 +49,3 @@ networks: volumes: pgdata: driver: local - dbeaver-data: - driver: local diff --git a/docker-compose/README.md b/docker-compose/README.md index 0f63a1b..bba1346 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -1,4 +1,4 @@ -#### Steps to Deploy: +# Steps to Deploy: 1. **Install Docker Compose:** Follow the [official Docker Compose installation guide](https://docs.docker.com/compose/install/). From a4f7964c06db5e4589a3fc7574f0bd4ba49c67e4 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 3 Feb 2025 08:22:46 -0500 Subject: [PATCH 19/51] added --- terraform/README.md | 2 +- terraform/ecs.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index b999b0d..24f8d16 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -144,7 +144,7 @@ Example wallet JSON format: } ``` -Note: The wallet JSON should be pasted directly into the terraform.tfvars file using the heredoc syntax (< Date: Mon, 3 Feb 2025 08:31:09 -0500 Subject: [PATCH 20/51] added --- .gitignore | 1 + README.md | 4 +- docker-compose/docker-compose.yml | 10 +- execution-role-policy.json | 12 - orchestrator/package.json | 2 +- orchestrator/src/app.ts | 516 +++++++++++++++++++----------- orchestrator/src/clear_outputs.ts | 8 +- orchestrator/src/ecs_config.ts | 25 +- orchestrator/src/stake.ts | 41 +++ requester/src/app.ts | 2 +- task-definition.json | 76 ----- task-role-policy.json | 12 - terraform/README.md | 16 +- terraform/terraform.tfvars | 34 -- 14 files changed, 426 insertions(+), 333 deletions(-) delete mode 100644 execution-role-policy.json create mode 100644 orchestrator/src/stake.ts delete mode 100644 task-definition.json delete mode 100644 task-role-policy.json delete mode 100644 terraform/terraform.tfvars diff --git a/.gitignore b/.gitignore index ca600aa..c8e7a03 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ node_modules/ *.exe *.lock.* LICENSE.txt +terraform/terraform.tfvars # Ignore package locks and dependency files diff --git a/README.md b/README.md index 688ee9b..17e5b7f 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ This option may cost more and depends on the uptime of the hardware you use. It ## Graceful Shutdown Policy To avoid being slashed, it is critical to run the graceful shutdown in the event of downtime. Failing to do so may result in penalties. -To run this go to "TODO" and navigate to your node and select the "SHUT DOWN" button and sign the transaction. +To run this go to ar://randao and navigate to your node and select the "SHUT DOWN" button and sign the transaction. This will tell your provider to stop serving random. After the maintinance is done and your provider is back up again click "START UP" button. @@ -77,7 +77,7 @@ After successfully setting up your node, you will need to provide proof that the 2. Upon confirmation, Ethan will provide your provider address with the necessary funds. -3. Navigate to [Insert link here] to stake your funds and configure your provider information. +3. Navigate to ar://randao to stake your funds and configure your provider information. By completing this process, you will fully activate your node and ensure it is ready for network participation. Thank you for contributing to the network's success! \ No newline at end of file diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 32100cb..5ed36c1 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +# version: '3.8' services: # Instance 1 @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.2.18 + image: randao/orchestrator:v0.2.46 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.2.18 + image: randao/orchestrator:v0.2.46 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.2.18 + image: randao/orchestrator:v0.2.46 depends_on: postgres3: condition: service_healthy @@ -133,7 +133,7 @@ services: - dbeaver-data:/opt/cloudbeaver/workspace requester: - image: randao/requester:v0.2.0 + image: randao/requester:v0.2.1 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/execution-role-policy.json b/execution-role-policy.json deleted file mode 100644 index b833d12..0000000 --- a/execution-role-policy.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] -} diff --git a/orchestrator/package.json b/orchestrator/package.json index 4238a12..959d2ce 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients":"3.5.17", + "ao-process-clients":"3.6.0", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 7af74d3..9072c4b 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -1,17 +1,19 @@ import { Client } from 'pg'; import Docker from 'dockerode'; import AWS from 'aws-sdk'; -import { getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomClientConfig } from "ao-process-clients" +import { Environment, GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomClientConfig } from "ao-process-clients" import { dbConfig } from './db_config.js'; +import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; + const RANDOM_CONFIG: RandomClientConfig = { tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", - processId: "KbaY8P4h9wdHYKHlBSLbXN_yd-9gxUDxSgBackUxTiQ", + processId: "yKVS1tYE3MajUpZqEIORmW1J8HTke-6o6o6tnlkFOZQ", wallet: JSON.parse(process.env.WALLET_JSON!), environment: 'mainnet' } //const randclient: IRandomClient = RandomClient.autoConfiguration() -const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG) +const randclient = new RandomClient(RANDOM_CONFIG) const docker = new Docker(); @@ -19,14 +21,15 @@ const docker = new Docker(); // Constants for configuration const POLLING_INTERVAL_MS = 5000; -const MINIMUM_ENTRIES = 250; -const TARGET_ENTRIES = 500; +const MINIMUM_ENTRIES = 500; +const DRYRUNTIMEOUT = 15000; // 15 seconds +const DRYRUNRESETTIME = 300000; // 5 min //Expected increments per second=10×0.005=0.05 //180 times per hour //4,320 times per day //1,576,800 times per year -const MAX_OUTSTANDING_REQUESTS = 50; -const MAX_OUTSTANDING_FULFILLMENTS = 50; +const MAX_OUTSTANDING_VDF_CONTAINERS = 10; +const RANDOM_PER_VDF = 10; const MAX_RETRIES = 10; const RETRY_DELAY_MS = 10000; const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.4'; @@ -34,15 +37,37 @@ const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks const ongoingContainers = new Set(); // Track container IDs of running Docker containers -const ongoingFulfillments = new Set(); // Track request IDs for ongoing fulfillments const PROVIDER_ID = process.env.PROVIDER_ID || "0"; const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "backend"; +const COMPLETION_RETENTION_PERIOD_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds let ongoingRequest = false; let spotInterruptions = 0; -let totalProvided = 0; let PreviousTotalAvailableRandom = 0; -const COMPLETION_RETENTION_PERIOD_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds +// Global variables to track polling status +let pollingInProgress = false; +let lastPollingId: string | null = null; +// Cache for network configuration +let cachedNetworkConfig: NetworkConfig | null = null; + + +interface StepTracking { + step1?: { completed: boolean; timeTaken: number }; + step2?: { completed: boolean; timeTaken: number }; + step3?: { completed: boolean; timeTaken: number }; + step4?: { completed: boolean; timeTaken: number }; +} +let stepTracking: StepTracking = {}; // Tracks the status and time for each step + +// Function to reset step tracking data +function resetStepTracking() { + stepTracking = { + step1: { completed: false, timeTaken: 0 }, + step2: { completed: false, timeTaken: 0 }, + step3: { completed: false, timeTaken: 0 }, + step4: { completed: false, timeTaken: 0 }, + }; +} // Retry logic for connecting to PostgreSQL async function connectWithRetry(): Promise { @@ -75,8 +100,8 @@ async function setupDatabase(client: Client): Promise { ); `); - // Ensure detected_completed column exists in case the table was created before it was added - await client.query(` + // Ensure detected_completed column exists in case the table was created before it was added + await client.query(` ALTER TABLE verifiable_delay_functions ADD COLUMN IF NOT EXISTS detected_completed TIMESTAMP NULL; `); @@ -84,17 +109,11 @@ async function setupDatabase(client: Client): Promise { } -import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; - -// Cache for network configuration -let cachedNetworkConfig: NetworkConfig | null = null; - -// Modified function to trigger VDF job pod using ECS or Docker async function triggerVDFJobPod(): Promise { if (ENVIRONMENT === 'cloud') { try { console.log("Cloud environment detected. Launching ECS task."); - + // Get or refresh network configuration if (!cachedNetworkConfig) { console.log("Fetching network configuration..."); @@ -102,7 +121,7 @@ async function triggerVDFJobPod(): Promise { console.log("Network config:", cachedNetworkConfig); } - const taskArn = await launchVDFTask(ecs, cachedNetworkConfig); + const taskArn = await launchVDFTask(ecs, cachedNetworkConfig, RANDOM_PER_VDF); if (taskArn) { ongoingTasks.add(taskArn); console.log(`ECS task started successfully: ${taskArn}`); @@ -121,32 +140,44 @@ async function triggerVDFJobPod(): Promise { return null; } } else { - const containerName = `vdf_job_${Date.now()}_${Math.floor(Math.random() * 10000)}`; - console.log(`Starting Docker container with name: ${containerName}`); - const container = await docker.createContainer({ - Image: VDF_JOB_IMAGE, - Cmd: ['python', 'main.py'], - Env: [ - `DATABASE_TYPE=postgresql`, - `DATABASE_HOST=${dbConfig.host}`, - `DATABASE_PORT=${dbConfig.port.toString()}`, - `DATABASE_USER=${dbConfig.user}`, - `DATABASE_PASSWORD=${dbConfig.password}`, - `DATABASE_NAME=${dbConfig.database}`, - ], - HostConfig: { - NetworkMode: DOCKER_NETWORK, - }, - name: containerName - }); + // Check if we have reached the maximum number of containers + if (ongoingContainers.size >= MAX_OUTSTANDING_VDF_CONTAINERS) { + console.log(`Maximum outstanding VDF containers (${MAX_OUTSTANDING_VDF_CONTAINERS}) reached. Not starting new container.`); + return null; + } - await container.start(); - ongoingContainers.add(container.id); - console.log(`Docker container ${containerName} started successfully.`); - return container.id; + const containerName = `vdf_job_${Date.now()}_${Math.floor(Math.random() * 100000)}`; + console.log(`Starting Docker container with name: ${containerName}`); + try { + const container = await docker.createContainer({ + Image: VDF_JOB_IMAGE, + Cmd: ['sh', '-c', `for i in $(seq 1 ${RANDOM_PER_VDF}); do python main.py; done`], + Env: [ + `DATABASE_TYPE=postgresql`, + `DATABASE_HOST=${dbConfig.host}`, + `DATABASE_PORT=${dbConfig.port.toString()}`, + `DATABASE_USER=${dbConfig.user}`, + `DATABASE_PASSWORD=${dbConfig.password}`, + `DATABASE_NAME=${dbConfig.database}`, + ], + HostConfig: { + NetworkMode: DOCKER_NETWORK, + }, + name: containerName + }); + + await container.start(); + ongoingContainers.add(container.id); + console.log(`Docker container ${containerName} started successfully.`); + return container.id; + } catch (error) { + console.error(`Error starting Docker container ${containerName}:`, error); + return null; + } } } + // Modified function to wait for ECS tasks to complete and remove them from tracking async function monitorECSTasks(): Promise { if (ongoingTasks.size === 0) return; @@ -181,34 +212,38 @@ async function monitorECSTasks(): Promise { // Function to wait for Docker containers to complete and remove them from tracking async function monitorDockerContainers(): Promise { if (ongoingContainers.size === 0) return; + if (ENVIRONMENT === 'cloud') { + await monitorECSTasks(); + } else { - for (const containerId of ongoingContainers) { - try { - const container = docker.getContainer(containerId); - const containerInfo = await container.inspect(); - - // Check if the container is already stopped (exited) - if (containerInfo.State.Status === 'exited') { - console.log(`Docker container stopped: ${containerId}`); - - // Attempt to remove the container, handling possible errors gracefully - try { - await container.remove({ force: true }); // Force removal to avoid "in progress" errors - console.log(`Docker container removed: ${containerId}`); - ongoingContainers.delete(containerId); - } catch (removeError) { - if (isDockerError(removeError) && removeError.statusCode === 409) { - // Error 409 means removal is in progress, so skip this container for now - console.log(`Removal of container ${containerId} is already in progress. Skipping.`); - } else { - // Handle other errors that might occur during container removal - console.error(`Error removing Docker container ${containerId}:`, removeError); + for (const containerId of ongoingContainers) { + try { + const container = docker.getContainer(containerId); + const containerInfo = await container.inspect(); + + // Check if the container is already stopped (exited) + if (containerInfo.State.Status === 'exited') { + console.log(`Docker container stopped: ${containerId}`); + + // Attempt to remove the container, handling possible errors gracefully + try { + await container.remove({ force: true }); // Force removal to avoid "in progress" errors + console.log(`Docker container removed: ${containerId}`); + ongoingContainers.delete(containerId); + } catch (removeError) { + if (isDockerError(removeError) && removeError.statusCode === 409) { + // Error 409 means removal is in progress, so skip this container for now + console.log(`Removal of container ${containerId} is already in progress. Skipping.`); + } else { + // Handle other errors that might occur during container removal + console.error(`Error removing Docker container ${containerId}:`, removeError); + } } } + } catch (error) { + console.error(`Error inspecting Docker container ${containerId}:`, error); + ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) } - } catch (error) { - console.error(`Error inspecting Docker container ${containerId}:`, error); - ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) } } } @@ -222,16 +257,16 @@ function isDockerError(error: unknown): error is { statusCode: number } { function hexMod64Bit(expectedOutput: string): { expectedOutput64BitBase10: string } { // Parse the hexadecimal string into a BigInt const number = BigInt(`0x${expectedOutput}`); - + // Define the 64-bit modulus (2^64 - 1) const modulus = BigInt("0x7FFFFFFFF"); - + // Keep dividing by modulus until we get a remainder less than modulus let remainder = number; while (remainder >= modulus) { remainder = remainder % modulus; } - + // Return the remainder in base 10 return { expectedOutput64BitBase10: remainder.toString(), @@ -243,55 +278,99 @@ function addHexPrefix(value: string): string { return value.startsWith('0x') ? value : `0x${value}`; } -// Function to check if there are fewer than MINIMUM_ENTRIES and fetch until TARGET_ENTRIES -async function checkAndFetchIfNeeded(client: Client): Promise { - try { - if (ongoingRequest) return; - const res = await client.query('SELECT COUNT(*) AS count FROM verifiable_delay_functions WHERE request_id IS NULL'); - const currentCount = parseInt(res.rows[0].count, 10); - console.log("Total usable db entries: " + currentCount); - if(PreviousTotalAvailableRandom !=currentCount){ - console.log(`Updating avalible random values from ${PreviousTotalAvailableRandom} to ${currentCount}`) - randclient.updateProviderAvailableValues(currentCount) - PreviousTotalAvailableRandom = currentCount; - } - - if (currentCount < MINIMUM_ENTRIES) { - const entriesNeeded = TARGET_ENTRIES - currentCount; - console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries to reach ${TARGET_ENTRIES}...`); - ongoingRequest = true; - - const batchCount = Math.min(entriesNeeded, MAX_OUTSTANDING_REQUESTS); - console.log(`Batchcount: ${batchCount}, Ongoing containers: ${ongoingContainers.size}`); - - let spawnCount = Math.min(batchCount, MAX_OUTSTANDING_REQUESTS - ongoingContainers.size); - if (spawnCount <= 0) { - console.log("Max outstanding containers reached. Skipping new container launches."); - return; - } - - for (let i = 0; i < spawnCount; i++) { - if (ENVIRONMENT === 'cloud' && ongoingTasks.size < MAX_OUTSTANDING_REQUESTS) { - triggerVDFJobPod().then(taskArn => { - if (taskArn) console.log(`ECS task triggered: ${taskArn}`); - }).catch(console.error); - } else if (ENVIRONMENT !== 'cloud' && ongoingContainers.size < MAX_OUTSTANDING_REQUESTS) { - triggerVDFJobPod().then(containerId => { - if (containerId) console.log(`Docker container triggered: ${containerId}`); - }).catch(console.error); +function updateAvailableValuesAsync(currentCount: number) { + return (async () => { + try { + await randclient.updateProviderAvailableValues(currentCount); + console.log(`Updated provider values to ${currentCount}`); + } catch (error) { + console.error("Failed to update provider values:", error); + } + })(); +} + +async function getMoreRandom(currentCount: number) { + const entriesNeeded = MINIMUM_ENTRIES - currentCount; + console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries...`); + + ongoingRequest = true; + + // Calculate how many containers to spawn + const possibleBatchCount = Math.ceil(entriesNeeded / RANDOM_PER_VDF); + const availableSpawns = Math.min( + possibleBatchCount, + MAX_OUTSTANDING_VDF_CONTAINERS - ongoingContainers.size + ); + + if (availableSpawns <= 0) { + console.log("Max outstanding containers reached. Skipping new container launches."); + ongoingRequest = false; + return; + } + + console.log(`Spawning up to ${availableSpawns} containers to generate random values.`); + + for (let i = 0; i < availableSpawns; i++) { + try { + const jobId = await triggerVDFJobPod(); + if (jobId) { + console.log(`Job triggered: ${jobId}`); + ongoingContainers.add(jobId); } + } catch (error) { + console.error('Error triggering job pod:', error); } + } +} + + +// Function to check and fetch database entries as needed +async function checkAndFetchIfNeeded(client: Client) { + try { + //Check if provider has been given a special signal + const on_chain_avalible_random = await getProviderAvailableRandomValues(PROVIDER_ID); - ongoingRequest = false; // Immediately allow other operations + // Query current count of usable DB entries + const res = await client.query( + 'SELECT COUNT(*) AS count FROM verifiable_delay_functions WHERE request_id IS NULL' + ); + const currentCount = parseInt(res.rows[0].count, 10); + console.log("Total usable DB entries: " + currentCount); + + switch (on_chain_avalible_random.availibleRandomValues) { + case -1: + console.log("Provider is shutting down"); + //TODO prepare for shutdown + break; + case -2: + console.log("Value is -2"); + break; + case -3: + console.log("Value is -3"); + break; + default: + console.log("Value is not -1, -2, or -3"); + if (PreviousTotalAvailableRandom !== currentCount) { + console.log(`Updating available random values from ${PreviousTotalAvailableRandom} to ${currentCount}`); + updateAvailableValuesAsync(currentCount); + PreviousTotalAvailableRandom = currentCount; + } } + if (ongoingRequest) return; // Prevent redundant operations + + // Check if more entries are needed + if (currentCount >= MINIMUM_ENTRIES) return; + getMoreRandom(currentCount) + } catch (error) { console.error('Error during check and fetch:', error); - ongoingRequest = false; + } finally { + ongoingRequest = false; // Allow future operations } } // Function to post VDF challenge (fetches dbId dynamically) -async function fulfillRandomChallenge(client: Client, requestId: string): Promise { +async function fulfillRandomChallenge(client: Client, requestId: string, parentLogId: string): Promise { try { // Fetch the necessary details from the database using requestId const res = await client.query( @@ -308,24 +387,24 @@ async function fulfillRandomChallenge(client: Client, requestId: string): Promis const { id: dbId, modulus, input } = res.rows[0]; - console.log(`Fetched entry details - Request ID: ${requestId}, DB ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); + console.log(`${parentLogId} Fetched entry details - Request ID: ${requestId}, DB ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); // Add hex prefix to modulus and input const hexModulus = addHexPrefix(modulus); const hexInput = addHexPrefix(input); - console.log(`Posting VDF challenge for Request ID: ${requestId}, DB ID: ${dbId}`); + console.log(`${parentLogId} Posting VDF challenge for Request ID: ${requestId}, DB ID: ${dbId}`); await randclient.postVDFChallenge(requestId, hexModulus, hexInput); - console.log(`Challenge posted for Request ID: ${requestId}. Waiting to post proof...`); + console.log(`${parentLogId} Challenge posted for Request ID: ${requestId}. Waiting to post proof...`); } catch (error) { - console.error(`Error posting VDF challenge for Request ID: ${requestId}:`, error); + console.error(`${parentLogId} Error posting VDF challenge for Request ID: ${requestId}:`, error); } } // Function to post VDF output and proof -async function fulfillRandomOutput(client: Client, requestId: string): Promise { +async function fulfillRandomOutput(client: Client, requestId: string, parentLogId: string): Promise { try { // Fetch the output and proof from the database using the requestId const res = await client.query('SELECT id, output, proof FROM verifiable_delay_functions WHERE request_id = $1', [requestId]); @@ -335,11 +414,11 @@ async function fulfillRandomOutput(client: Client, requestId: string): Promise { + + let openRequests: GetOpenRandomRequestsResponse; -async function polling(client: Client): Promise { + // Create a function to fetch open requests with a timeout + const fetchOpenRequests = async (): Promise => { + try { + const response = await randclient.getOpenRandomRequests(PROVIDER_ID); + return response; + } catch (error) { + console.error(`${parentLogId} Error fetching requests: ${error}`); + return { /* Return a default or empty response here */ } as GetOpenRandomRequestsResponse; + } + }; + + try { + openRequests = await Promise.race([ + fetchOpenRequests().catch(err => { + throw new Error(`${parentLogId} Fetch Error: ${err}`); + }), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), DRYRUNTIMEOUT) + ) + ]); + } catch (error) { + console.log(`${parentLogId} Step 1: ${error}`); + randclient.setDryRunAsMessage(true); + console.log("Switching dryrun off"); + openRequests = await fetchOpenRequests(); // Retry request + } + + console.log(`${parentLogId} Step 1: Open Requests: ${JSON.stringify(openRequests)}`); + console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${openRequests.activeChallengeRequests.request_ids.length}`); + console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${openRequests.activeOutputRequests.request_ids.length}`); + return openRequests; +} + +//Todo timeout the second anddefault to 0 +async function getProviderAvailableRandomValues(PROVIDER_ID: string): Promise { + let avalibleRandom: GetProviderAvailableValuesResponse; + // Create a function to fetch open requests with a timeout + const fetchAvalibleRandom = async (): Promise => { + try { + const response = await randclient.getProviderAvailableValues(PROVIDER_ID); + return response; + } catch (error) { + console.error(`Error fetching avalible random: ${error}`); + return { /* Return a default or empty response here */ } as GetProviderAvailableValuesResponse; + } + }; + + try { + avalibleRandom = await Promise.race([ + fetchAvalibleRandom().catch(err => { + throw new Error(`Fetch Error: ${err}`); + }), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), DRYRUNTIMEOUT) + ) + ]); + } catch (error) { + // console.log(`${parentLogId} Step 1: ${error}`); + randclient.setDryRunAsMessage(true); + console.log("Switching dryrun off"); + avalibleRandom = await fetchAvalibleRandom(); // Retry request + } + return avalibleRandom; +} + + + +async function polling(client: any) { if (pollingInProgress) { - console.log(`[SKIPPED] Polling is already in progress. Skipping this run.`); + const completedSteps = Object.entries(stepTracking) + .filter(([_, data]) => data?.completed) + .map(([step, data]) => `${step} (Time: ${data?.timeTaken}ms)`); + + console.log(`\n[SKIPPED] Polling already in progress for ${lastPollingId}. Skipping this run.`); + console.log(`Completed steps so far: ${completedSteps.length > 0 ? completedSteps.join(", ") : "None"}`); + console.log("Current step tracking status:", stepTracking); // Debugging info to inspect tracking object return; // Prevent concurrent execution } + resetStepTracking(); // Reset step tracking for fresh polling pollingInProgress = true; // Mark polling as in progress const logId = getLogId(); + lastPollingId = logId; console.log(`${logId} Starting Polling...`); try { const startTime = Date.now(); // Start time of polling - console.log(`${logId} Step 1: Fetching open requests from the Randomness Client.`); - const step1Start = Date.now(); - const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); - const step1End = Date.now(); - console.log(`${logId} Step 1: Open Requests: ${JSON.stringify(openRequests)}`); - console.log(openRequests); - console.log(`${logId} Step 1: Open requests fetched. Time taken: ${(step1End - step1Start)}ms`); + // Step 1: Fetch open requests + const s1 = Date.now(); + console.log(`${logId} Step 1 started.`); + const openRequests = await getProviderRequests(PROVIDER_ID, logId); + stepTracking.step1 = { completed: true, timeTaken: Date.now() - s1 }; + console.log(`${logId} Step 1: Open requests fetched. Time taken: ${stepTracking.step1.timeTaken}ms`); - // Run Step 2, 3, and 4 concurrently after Step 1 - const step2Start = Date.now(); + // Run Step 2, 3, and 4 concurrently await Promise.all([ (async () => { const s2 = Date.now(); + console.log(`${logId} Step 2 started.`); await processChallengeRequests(client, openRequests.activeChallengeRequests, logId); - console.log(`${logId} Step 2 completed. Time taken: ${Date.now() - s2}ms`); + stepTracking.step2 = { completed: true, timeTaken: Date.now() - s2 }; + console.log(`${logId} Step 2 completed. Time taken: ${stepTracking.step2.timeTaken}ms`); })(), (async () => { const s3 = Date.now(); + console.log(`${logId} Step 3 started.`); await processOutputRequests(client, openRequests.activeOutputRequests, logId); - console.log(`${logId} Step 3 completed. Time taken: ${Date.now() - s3}ms`); + stepTracking.step3 = { completed: true, timeTaken: Date.now() - s3 }; + console.log(`${logId} Step 3 completed. Time taken: ${stepTracking.step3.timeTaken}ms`); })(), (async () => { const s4 = Date.now(); + console.log(`${logId} Step 4 started.`); await cleanupFulfilledEntries(client, openRequests, logId); - console.log(`${logId} Step 4 completed. Time taken: ${Date.now() - s4}ms`); + stepTracking.step4 = { completed: true, timeTaken: Date.now() - s4 }; + console.log(`${logId} Step 4 completed. Time taken: ${stepTracking.step4.timeTaken}ms`); })(), ]); - const step2End = Date.now(); - const totalTime = step2End - startTime; + const totalTime = Date.now() - startTime; console.log(`${logId} Polling cycle completed successfully. Total time taken: ${totalTime}ms`); } catch (error) { @@ -425,39 +582,37 @@ async function processChallengeRequests( activeChallengeRequests: { request_ids: string[] } | undefined, parentLogId: string ): Promise { - const logId = getLogId(); - console.log(`${logId} Step 2: Processing challenge requests.`); + console.log(`${parentLogId} Step 2: Processing challenge requests.`); if (!activeChallengeRequests || activeChallengeRequests.request_ids.length === 0) { - console.log(`${logId} No Challenge Requests to process.`); + console.log(`${parentLogId} No Challenge Requests to process.`); return; } - // Limit to MAX_OUTSTANDING_FULFILLMENTS requests - const requestIds = activeChallengeRequests.request_ids.slice(0, MAX_OUTSTANDING_FULFILLMENTS); - console.log(`${logId} Processing up to ${requestIds.length} requests.`); + const requestIds = activeChallengeRequests.request_ids; + console.log(`${parentLogId} Processing up to ${requestIds.length} requests.`); try { await client.query('BEGIN'); // Start transaction - console.log(`${logId} Fetching existing request mappings.`); + console.log(`${parentLogId} Fetching existing request mappings.`); // Fetch already assigned request_id -> dbId mappings const existingMappingsRes = await client.query( `SELECT request_id FROM verifiable_delay_functions WHERE request_id = ANY($1) - FOR UPDATE SKIP LOCKED`, + FOR UPDATE SKIP LOCKED`, [requestIds] ); const existingRequestIds = new Set(existingMappingsRes.rows.map(row => row.request_id)); - console.log(`${logId} Found ${existingRequestIds.size} already mapped requests.`); + console.log(`${parentLogId} Found ${existingRequestIds.size} already mapped requests.`); // Find only the unmapped requests (requestIds not in existingRequestIds) const unmappedRequestIds = requestIds.filter(requestId => !existingRequestIds.has(requestId)); - console.log(`${logId} Unmapped requests: ${unmappedRequestIds.length}`); + console.log(`${parentLogId} Unmapped requests: ${unmappedRequestIds.length}`); // Fetch available DB entries for unmapped requests - console.log(`${logId} Fetching available DB entries.`); + console.log(`${parentLogId} Fetching available DB entries.`); const dbRes = await client.query( `SELECT id FROM verifiable_delay_functions WHERE request_id IS NULL @@ -468,16 +623,16 @@ async function processChallengeRequests( ); const availableDbEntries = dbRes.rows.map(row => row.id); - console.log(`${logId} Found ${availableDbEntries.length} available DB entries.`); + console.log(`${parentLogId} Found ${availableDbEntries.length} available DB entries.`); // Reduce request list if we don’t have enough DB entries if (availableDbEntries.length < unmappedRequestIds.length) { - console.log(`${logId} Limiting requests to ${availableDbEntries.length} due to DB availability.`); + console.log(`${parentLogId} Limiting requests to ${availableDbEntries.length} due to DB availability.`); unmappedRequestIds.length = availableDbEntries.length; } if (availableDbEntries.length === 0 && existingRequestIds.size === 0) { - console.log(`${logId} No available DB entries to process and no existing mappings.`); + console.log(`${parentLogId} No available DB entries to process and no existing mappings.`); await client.query('COMMIT'); // Commit to release locks return; } @@ -490,52 +645,51 @@ async function processChallengeRequests( WHERE id = $2`, [unmappedRequestIds[i], availableDbEntries[i]] ); - console.log(`${logId} Assigned Request ID ${unmappedRequestIds[i]} to DB Entry ${availableDbEntries[i]}.`); + console.log(`${parentLogId} Assigned Request ID ${unmappedRequestIds[i]} to DB Entry ${availableDbEntries[i]}.`); } await client.query('COMMIT'); // Commit all updates at once // Call fulfillRandomChallenge for all request IDs (existing + newly mapped) - for (const requestId of requestIds) { - fulfillRandomChallenge(client, requestId) - .catch(error => console.error(`${logId} Error fulfilling challenge for Request ID ${requestId}:`, error)); - } + // Create an array of promises and use Promise.all to await them all in parallel + const promises = requestIds.map(requestId => + fulfillRandomChallenge(client, requestId, parentLogId) + .catch(error => console.error(`${parentLogId} Error fulfilling challenge for Request ID ${requestId}:`, error)) + ); + + await Promise.all(promises); // Wait for all promises to resolve + console.log(`${parentLogId} All challenges fulfilled`); - console.log(`${logId} Step 2 completed.`); + console.log(`${parentLogId} Step 2 completed.`); } catch (error) { - console.error(`${logId} Error in processChallengeRequests:`, error); + console.error(`${parentLogId} Error in processChallengeRequests:`, error); await client.query('ROLLBACK'); // Rollback on failure } } - - - - // Step 3: Process Output Requests (unchanged but with logging) async function processOutputRequests( client: Client, activeOutputRequests: { request_ids: string[] } | undefined, parentLogId: string ): Promise { - const logId = getLogId(); - console.log(`${logId} Step 3: Processing output requests.`); + console.log(`${parentLogId} Step 3: Processing output requests.`); if (!activeOutputRequests || activeOutputRequests.request_ids.length === 0) { - console.log(`${logId} No Output Requests to process.`); + console.log(`${parentLogId} No Output Requests to process.`); return; } const outputPromises = activeOutputRequests.request_ids.map(async (requestId) => { - console.log(`${logId} Processing output request ID: ${requestId}`); + console.log(`${parentLogId} Processing output request ID: ${requestId}`); // Run fulfillRandomOutput asynchronously (do not await) - fulfillRandomOutput(client, requestId) - .catch(error => console.error(`${logId} Error fulfilling output:`, error)); + fulfillRandomOutput(client, requestId, parentLogId) + .catch(error => console.error(`${parentLogId} Error fulfilling output:`, error)); }); await Promise.all(outputPromises); - console.log(`${logId} Step 3 completed.`); + console.log(`${parentLogId} Step 3 completed.`); } // Step 4: Remove fulfilled entries no longer in use (unchanged but with logging) @@ -544,8 +698,7 @@ async function cleanupFulfilledEntries( openRequests: any, parentLogId: string ): Promise { - const logId = getLogId(); - console.log(`${logId} Step 4: Checking for fulfilled entries no longer in use.`); + console.log(`${parentLogId} Step 4: Checking for fulfilled entries no longer in use.`); const now = new Date(); const cutoffTime = new Date(now.getTime() - COMPLETION_RETENTION_PERIOD_MS); @@ -588,7 +741,7 @@ async function cleanupFulfilledEntries( SET detected_completed = NOW() WHERE id = ANY($1) `, [markAsCompleted]); - console.log(`${logId} Marked ${markAsCompleted.length} entries as completed.`); + console.log(`${parentLogId} Marked ${markAsCompleted.length} entries as completed.`); } // Delete old completed entries @@ -597,16 +750,16 @@ async function cleanupFulfilledEntries( DELETE FROM verifiable_delay_functions WHERE id = ANY($1) `, [markForDeletion]); - console.log(`${logId} Deleted ${markForDeletion.length} old completed entries.`); + console.log(`${parentLogId} Deleted ${markForDeletion.length} old completed entries.`); } await client.query('COMMIT'); } catch (error) { - console.error(`${logId} Error in cleanupFulfilledEntries:`, error); + console.error(`${parentLogId} Error in cleanupFulfilledEntries:`, error); await client.query('ROLLBACK'); } - console.log(`${logId} Step 4 completed.`); + console.log(`${parentLogId} Step 4 completed.`); } @@ -620,22 +773,27 @@ async function run(): Promise { const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); console.log(`Periodic log - Current database size: ${res.rows[0].count}`); // Check and fetch entries for the database if needed - console.log("Step 1: Checking and fetching database entries if below threshold."); + console.log("Step 0: Checking and fetching database entries if below threshold."); checkAndFetchIfNeeded(client).catch((error) => { console.error("Error in checkAndFetchIfNeeded:", error); }); - + }, 10000); setInterval(async () => { await monitorDockerContainers(); - await monitorECSTasks(); }, 30000); // Cleanup every 30 seconds setInterval(async () => { await polling(client); }, POLLING_INTERVAL_MS); + setInterval(async () => { + randclient.setDryRunAsMessage(false); + console.log("Switching dryrun on") + }, DRYRUNRESETTIME); + + process.on("SIGTERM", async () => { console.log("SIGTERM received. Closing database connection."); await client.end(); diff --git a/orchestrator/src/clear_outputs.ts b/orchestrator/src/clear_outputs.ts index d7ee4c3..a554e4d 100644 --- a/orchestrator/src/clear_outputs.ts +++ b/orchestrator/src/clear_outputs.ts @@ -4,11 +4,11 @@ import { dbConfig } from './db_config'; // Random Client Configuration const RANDOM_CONFIG: RandomClientConfig = { - tokenProcessId: "7enZBOhWsyU3A5oCt8HtMNNPHSxXYJVTlOGOetR9IDw", - processId: "KbaY8P4h9wdHYKHlBSLbXN_yd-9gxUDxSgBackUxTiQ", + tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", + processId: "yKVS1tYE3MajUpZqEIORmW1J8HTke-6o6o6tnlkFOZQ", wallet: JSON.parse(process.env.WALLET_JSON!), - environment: 'mainnet' as const -}; + environment: 'mainnet' +} const randclient = new RandomClient(RANDOM_CONFIG); const PROVIDER_ID = process.env.PROVIDER_ID || "0"; diff --git a/orchestrator/src/ecs_config.ts b/orchestrator/src/ecs_config.ts index 18f30db..e97184a 100644 --- a/orchestrator/src/ecs_config.ts +++ b/orchestrator/src/ecs_config.ts @@ -34,26 +34,39 @@ export async function getNetworkConfig(ecs: AWS.ECS): Promise { }; } -export async function launchVDFTask(ecs: AWS.ECS, networkConfig: NetworkConfig): Promise { +export async function launchVDFTask( + ecs: AWS.ECS, + networkConfig: NetworkConfig, + random_per_vdf: number, +): Promise { const result = await ecs.runTask({ cluster: process.env.ECS_CLUSTER_NAME, taskDefinition: 'vdf-job', capacityProviderStrategy: [ { capacityProvider: 'FARGATE_SPOT', - weight: 1 - } + weight: 1, + }, ], networkConfiguration: { awsvpcConfiguration: { subnets: networkConfig.subnets, securityGroups: networkConfig.securityGroups, - assignPublicIp: 'ENABLED' - } + assignPublicIp: 'ENABLED', + }, + }, + count: 1, + overrides: { + containerOverrides: [ + { + name: 'vdf_job_container', // Must match the container name in the task definition + command: ['sh', '-c', `for i in $(seq 1 ${random_per_vdf}); do python main.py; done`], + }, + ], }, - count: 1 }).promise(); const taskArn = result.tasks?.[0]?.taskArn; return taskArn || null; } + diff --git a/orchestrator/src/stake.ts b/orchestrator/src/stake.ts new file mode 100644 index 0000000..9ed352f --- /dev/null +++ b/orchestrator/src/stake.ts @@ -0,0 +1,41 @@ +import { getRandomClientAutoConfiguration, RandomClient, RandomClientConfig, StakingClient, } from "ao-process-clients"; +import { ProviderDetails } from "ao-process-clients/dist/src/clients/staking/abstract/types"; + +// Random Client Configuration +const RANDOM_CONFIG: RandomClientConfig = { + tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", + processId: "yKVS1tYE3MajUpZqEIORmW1J8HTke-6o6o6tnlkFOZQ", + wallet: JSON.parse(process.env.WALLET_JSON!), + environment: 'mainnet' +} + +const randclient = new StakingClient(RANDOM_CONFIG); + +// Function to clear all output requests +async function stake() { + try { + let providerDetails: ProviderDetails = { /** Provider name */ + name: "test", + /** Commission percentage (1-100) */ + commission: 50, + /** Provider description */ + description: "this is a test description", + /** Optional Twitter handle */ + twitter: "test_twitter", + /** Optional Discord handle */ + discord: "test_discord", + /** Optional Telegram handle */ + telegram: "test_tg"}; + console.log(await randclient.stake("100000000000000000000",providerDetails)) + } catch (error) { + console.error("An error occurred while staking:", error); + } finally { + + console.log("Done."); + } +} + +// Run the function when the script is executed +(async () => { + await stake(); +})(); diff --git a/requester/src/app.ts b/requester/src/app.ts index 99b2bd6..142f2d2 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -17,7 +17,7 @@ const CHANCE_TO_CALL_RANDOM = 1; const RANDOM_CONFIG: RandomClientConfig = { tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", - processId: "KbaY8P4h9wdHYKHlBSLbXN_yd-9gxUDxSgBackUxTiQ", + processId: "yKVS1tYE3MajUpZqEIORmW1J8HTke-6o6o6tnlkFOZQ", wallet: JSON.parse(process.env.REQUEST_WALLET_JSON!), environment: "mainnet", }; diff --git a/task-definition.json b/task-definition.json deleted file mode 100644 index cf7e617..0000000 --- a/task-definition.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "family": "orchestrator-service", - "networkMode": "awsvpc", - "containerDefinitions": [ - { - "name": "postgres", - "image": "postgres:13", - "essential": true, - "environment": [ - { "name": "POSTGRES_USER", "value": "myuser" }, - { "name": "POSTGRES_PASSWORD", "value": "mypassword" }, - { "name": "POSTGRES_DB", "value": "mydatabase" } - ], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": "ecs-orchestrator-service", - "awslogs-region": "us-east-1", - "awslogs-stream-prefix": "postgres" - } - }, - "healthCheck": { - "command": [ - "CMD-SHELL", - "pg_isready -U myuser -d mydatabase" - ], - "interval": 10, - "timeout": 5, - "retries": 5 - }, - "mountPoints": [ - { - "sourceVolume": "pgdata", - "containerPath": "/var/lib/postgresql/data" - } - ] - }, - { - "name": "orchestrator", - "image": "satoshispalace/orchestrator:latest", - "essential": true, - "environment": [ - { "name": "DB_HOST", "value": "postgres" }, - { "name": "DB_PORT", "value": "5432" }, - { "name": "DB_USER", "value": "myuser" }, - { "name": "DB_PASSWORD", "value": "mypassword" }, - { "name": "DB_NAME", "value": "mydatabase" } - ], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": "ecs-orchestrator-service", - "awslogs-region": "us-east-1", - "awslogs-stream-prefix": "orchestrator" - } - }, - "mountPoints": [] - } - ], - "volumes": [ - { - "name": "pgdata", - "efsVolumeConfiguration": { - "fileSystemId": "fs-01a952c26605adac6", - "rootDirectory": "/" - } - } - ], - "requiresCompatibilities": [ - "FARGATE" - ], - "cpu": "512", - "memory": "1024", - "executionRoleArn": "arn:aws:iam::615299754404:role/orchestrator-service-execution-role", - "taskRoleArn": "arn:aws:iam::615299754404:role/orchestrator-service-task-role" -} diff --git a/task-role-policy.json b/task-role-policy.json deleted file mode 100644 index b833d12..0000000 --- a/task-role-policy.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] -} diff --git a/terraform/README.md b/terraform/README.md index 24f8d16..3b8fa5c 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -105,7 +105,21 @@ export AWS_REGION="your-region" # e.g., us-east-1 ``` -3. **Initialize and Apply Terraform Configuration:** + +4. **Set up ENV variables:** + Navigate to the Terraform directory of the project: + ```bash + cp terraform.tfvars.example terraform.tfvars + ``` + Fill in all of the variables with your info. + The only feilds that NEED to be filled in are + 1. provider_id + 2. local_wallet_json + + The Database configuration is highly suggested and the secrets can be left alone as its just the name of the secrets + + +5. **Initialize and Apply Terraform Configuration:** Navigate to the Terraform directory of the project and run: ```bash terraform init diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars deleted file mode 100644 index 9c228d6..0000000 --- a/terraform/terraform.tfvars +++ /dev/null @@ -1,34 +0,0 @@ -# AWS Region -aws_region = "us-east-1" - -# Provider Configuration -provider_id = "1zlA7nKecUGevGNAEbjim_SlbioOI6daNNn2luDEHb0" # Set this to your unique provider identifier - -# Database Configuration (for local development/testing) -local_db_user = "myuser" # Change this -local_db_password = "mypassword" # Change this -db_name = "orchestrator_db" - -# Wallet Configuration -# Option 1: Paste your wallet JSON directly (for testing only) -local_wallet_json = < Date: Mon, 3 Feb 2025 08:36:32 -0500 Subject: [PATCH 21/51] added --- .env.example | 8 -- .env.template | 23 ---- .gitignore | 2 +- docker-compose/2docker-compose.yml | 51 --------- docker-compose/README.md | 18 +++ docker-compose/dev-docker-compose.yml | 154 ++++++++++++++++++++++++++ docker-compose/docker-compose.yml | 145 ++++-------------------- terraform/README.md | 67 +++-------- 8 files changed, 209 insertions(+), 259 deletions(-) delete mode 100644 .env.example delete mode 100644 .env.template delete mode 100644 docker-compose/2docker-compose.yml create mode 100644 docker-compose/dev-docker-compose.yml diff --git a/.env.example b/.env.example deleted file mode 100644 index 9d969ba..0000000 --- a/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -DB_USER=myuser -DB_PASSWORD=mypassword -DB_NAME=mydatabase -DOCKER_NETWORK=backend -PATH_TO_WALLET="wallet.json" -PROVIDER_ID = "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE" -WALLET_JSON = '{}' -REQUEST_WALLET_JSON = '{}' \ No newline at end of file diff --git a/.env.template b/.env.template deleted file mode 100644 index b741487..0000000 --- a/.env.template +++ /dev/null @@ -1,23 +0,0 @@ -# Instance 1 -DB_USER_1=myuser1 -DB_PASSWORD_1=mypassword1 -DB_NAME_1=mydatabase1 -WALLET_JSON_1=your_wallet_json_1 -REQUEST_WALLET_JSON_1=your_request_wallet_json_1 -PROVIDER_ID_1=your_provider_id_1 - -# Instance 2 -DB_USER_2=myuser2 -DB_PASSWORD_2=mypassword2 -DB_NAME_2=mydatabase2 -WALLET_JSON_2=your_wallet_json_2 -REQUEST_WALLET_JSON_2=your_request_wallet_json_2 -PROVIDER_ID_2=your_provider_id_2 - -# Instance 3 -DB_USER_3=myuser3 -DB_PASSWORD_3=mypassword3 -DB_NAME_3=mydatabase3 -WALLET_JSON_3=your_wallet_json_3 -REQUEST_WALLET_JSON_3=your_request_wallet_json_3 -PROVIDER_ID_3=your_provider_id_3 diff --git a/.gitignore b/.gitignore index c8e7a03..8e1d01f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ node_modules/ *.exe *.lock.* LICENSE.txt -terraform/terraform.tfvars +terraform.tfvars # Ignore package locks and dependency files diff --git a/docker-compose/2docker-compose.yml b/docker-compose/2docker-compose.yml deleted file mode 100644 index d48a879..0000000 --- a/docker-compose/2docker-compose.yml +++ /dev/null @@ -1,51 +0,0 @@ -version: '3.8' - -services: - postgres: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER:-myuser} - POSTGRES_PASSWORD: ${DB_PASSWORD:-mypassword} - POSTGRES_DB: ${DB_NAME:-mydatabase} - ports: - - "5432:5432" - networks: - - backend - volumes: - - pgdata:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-myuser} -d ${DB_NAME:-mydatabase}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator: - image: randao/orchestrator:v0.2.18 - depends_on: - postgres: - condition: service_healthy - environment: - DB_HOST: postgres - DB_PORT: 5432 - DB_USER: ${DB_USER:-myuser} - DB_PASSWORD: ${DB_PASSWORD:-mypassword} - DB_NAME: ${DB_NAME:-mydatabase} - ENVIRONMENT: local - PATH_TO_WALLET: /app/wallet.json # Path inside the container - WALLET_JSON: ${WALLET_JSON} - PROVIDER_ID: ${PROVIDER_ID} - DOCKER_NETWORK: backend # Passing the network name - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock # Mount Docker socket - - ./wallet.json:/app/wallet.json # Mount wallet.json into the container - -networks: - backend: - name: backend # This will set the network name explicitly - driver: bridge - -volumes: - pgdata: - driver: local diff --git a/docker-compose/README.md b/docker-compose/README.md index bba1346..28bf61c 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -2,7 +2,25 @@ 1. **Install Docker Compose:** Follow the [official Docker Compose installation guide](https://docs.docker.com/compose/install/). + 2. **Deploy Node:** + Navigate to the Docker Compose directory and run: + ```bash + cp .env.example .env + ``` +Then fill in all of the variables: +Required variables: +- `provider_id`: Your unique provider identifier +- `local_db_user`: Database username +- `local_db_password`: Database password +- `local_wallet_json`: Wallet JSON (either direct or via file) + +Optional variables (with defaults): +- `aws_region`: AWS region (default: us-east-1) +- `db_name`: Database name (default: orchestrator_db) +- `secrets_prefix`: Prefix for secrets (default: /orchestrator) + +3. **Deploy Node:** Navigate to the Docker Compose directory and run: ```bash docker-compose up -d diff --git a/docker-compose/dev-docker-compose.yml b/docker-compose/dev-docker-compose.yml new file mode 100644 index 0000000..5ed36c1 --- /dev/null +++ b/docker-compose/dev-docker-compose.yml @@ -0,0 +1,154 @@ +# version: '3.8' + +services: + # Instance 1 + postgres1: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_1:-myuser1} + POSTGRES_PASSWORD: ${DB_PASSWORD_1:-mypassword1} + POSTGRES_DB: ${DB_NAME_1:-mydatabase1} + ports: + - "5432:5432" + networks: + - backend + volumes: + - pgdata1:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_1:-myuser1} -d ${DB_NAME_1:-mydatabase1}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator1: + image: randao/orchestrator:v0.2.46 + depends_on: + postgres1: + condition: service_healthy + environment: + DB_HOST: postgres1 + DB_PORT: 5432 + DB_USER: ${DB_USER_1:-myuser1} + DB_PASSWORD: ${DB_PASSWORD_1:-mypassword1} + DB_NAME: ${DB_NAME_1:-mydatabase1} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_1} + PROVIDER_ID: ${PROVIDER_ID_1} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 2 + postgres2: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_2:-myuser2} + POSTGRES_PASSWORD: ${DB_PASSWORD_2:-mypassword2} + POSTGRES_DB: ${DB_NAME_2:-mydatabase2} + ports: + - "5433:5432" + networks: + - backend + volumes: + - pgdata2:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_2:-myuser2} -d ${DB_NAME_2:-mydatabase2}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator2: + image: randao/orchestrator:v0.2.46 + depends_on: + postgres2: + condition: service_healthy + environment: + DB_HOST: postgres2 + DB_PORT: 5432 + DB_USER: ${DB_USER_2:-myuser2} + DB_PASSWORD: ${DB_PASSWORD_2:-mypassword2} + DB_NAME: ${DB_NAME_2:-mydatabase2} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_2} + PROVIDER_ID: ${PROVIDER_ID_2} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 3 + postgres3: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_3:-myuser3} + POSTGRES_PASSWORD: ${DB_PASSWORD_3:-mypassword3} + POSTGRES_DB: ${DB_NAME_3:-mydatabase3} + ports: + - "5434:5432" + networks: + - backend + volumes: + - pgdata3:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_3:-myuser3} -d ${DB_NAME_3:-mydatabase3}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator3: + image: randao/orchestrator:v0.2.46 + depends_on: + postgres3: + condition: service_healthy + environment: + DB_HOST: postgres3 + DB_PORT: 5432 + DB_USER: ${DB_USER_3:-myuser3} + DB_PASSWORD: ${DB_PASSWORD_3:-mypassword3} + DB_NAME: ${DB_NAME_3:-mydatabase3} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_3} + PROVIDER_ID: ${PROVIDER_ID_3} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Single DBeaver Instance + dbeaver: + image: dbeaver/cloudbeaver:23.2.0 + environment: + CB_SERVER_SERVER_PORT: 8080 + CB_SERVER_ADMIN_NAME: "admin" + CB_SERVER_ADMIN_PASSWORD: "admin123" + networks: + - backend + ports: + - "8080:8978" + volumes: + - dbeaver-data:/opt/cloudbeaver/workspace + + requester: + image: randao/requester:v0.2.1 + environment: + REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} + + +networks: + backend: + name: backend + driver: bridge + +volumes: + pgdata1: + driver: local + pgdata2: + driver: local + pgdata3: + driver: local + dbeaver-data: + driver: local diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 5ed36c1..b002fb5 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -1,154 +1,51 @@ -# version: '3.8' +version: '3.8' services: - # Instance 1 - postgres1: + postgres: image: postgres:13 environment: - POSTGRES_USER: ${DB_USER_1:-myuser1} - POSTGRES_PASSWORD: ${DB_PASSWORD_1:-mypassword1} - POSTGRES_DB: ${DB_NAME_1:-mydatabase1} + POSTGRES_USER: ${DB_USER:-myuser} + POSTGRES_PASSWORD: ${DB_PASSWORD:-mypassword} + POSTGRES_DB: ${DB_NAME:-mydatabase} ports: - "5432:5432" networks: - backend volumes: - - pgdata1:/var/lib/postgresql/data + - pgdata:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_1:-myuser1} -d ${DB_NAME_1:-mydatabase1}"] + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-myuser} -d ${DB_NAME:-mydatabase}"] interval: 10s timeout: 5s retries: 5 - orchestrator1: + orchestrator: image: randao/orchestrator:v0.2.46 depends_on: - postgres1: + postgres: condition: service_healthy environment: - DB_HOST: postgres1 + DB_HOST: postgres DB_PORT: 5432 - DB_USER: ${DB_USER_1:-myuser1} - DB_PASSWORD: ${DB_PASSWORD_1:-mypassword1} - DB_NAME: ${DB_NAME_1:-mydatabase1} + DB_USER: ${DB_USER:-myuser} + DB_PASSWORD: ${DB_PASSWORD:-mypassword} + DB_NAME: ${DB_NAME:-mydatabase} ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_1} - PROVIDER_ID: ${PROVIDER_ID_1} - DOCKER_NETWORK: backend + PATH_TO_WALLET: /app/wallet.json # Path inside the container + WALLET_JSON: ${WALLET_JSON} + PROVIDER_ID: ${PROVIDER_ID} + DOCKER_NETWORK: backend # Passing the network name networks: - backend volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 2 - postgres2: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_2:-myuser2} - POSTGRES_PASSWORD: ${DB_PASSWORD_2:-mypassword2} - POSTGRES_DB: ${DB_NAME_2:-mydatabase2} - ports: - - "5433:5432" - networks: - - backend - volumes: - - pgdata2:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_2:-myuser2} -d ${DB_NAME_2:-mydatabase2}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator2: - image: randao/orchestrator:v0.2.46 - depends_on: - postgres2: - condition: service_healthy - environment: - DB_HOST: postgres2 - DB_PORT: 5432 - DB_USER: ${DB_USER_2:-myuser2} - DB_PASSWORD: ${DB_PASSWORD_2:-mypassword2} - DB_NAME: ${DB_NAME_2:-mydatabase2} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_2} - PROVIDER_ID: ${PROVIDER_ID_2} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 3 - postgres3: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_3:-myuser3} - POSTGRES_PASSWORD: ${DB_PASSWORD_3:-mypassword3} - POSTGRES_DB: ${DB_NAME_3:-mydatabase3} - ports: - - "5434:5432" - networks: - - backend - volumes: - - pgdata3:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_3:-myuser3} -d ${DB_NAME_3:-mydatabase3}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator3: - image: randao/orchestrator:v0.2.46 - depends_on: - postgres3: - condition: service_healthy - environment: - DB_HOST: postgres3 - DB_PORT: 5432 - DB_USER: ${DB_USER_3:-myuser3} - DB_PASSWORD: ${DB_PASSWORD_3:-mypassword3} - DB_NAME: ${DB_NAME_3:-mydatabase3} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_3} - PROVIDER_ID: ${PROVIDER_ID_3} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Single DBeaver Instance - dbeaver: - image: dbeaver/cloudbeaver:23.2.0 - environment: - CB_SERVER_SERVER_PORT: 8080 - CB_SERVER_ADMIN_NAME: "admin" - CB_SERVER_ADMIN_PASSWORD: "admin123" - networks: - - backend - ports: - - "8080:8978" - volumes: - - dbeaver-data:/opt/cloudbeaver/workspace - - requester: - image: randao/requester:v0.2.1 - environment: - REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} - + - /var/run/docker.sock:/var/run/docker.sock # Mount Docker socket + - ./wallet.json:/app/wallet.json # Mount wallet.json into the container networks: backend: - name: backend + name: backend # This will set the network name explicitly driver: bridge volumes: - pgdata1: - driver: local - pgdata2: - driver: local - pgdata3: - driver: local - dbeaver-data: + pgdata: driver: local diff --git a/terraform/README.md b/terraform/README.md index 3b8fa5c..d5d73b8 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -112,9 +112,18 @@ cp terraform.tfvars.example terraform.tfvars ``` Fill in all of the variables with your info. - The only feilds that NEED to be filled in are - 1. provider_id - 2. local_wallet_json +## Variables Reference + +Required variables: +- `provider_id`: Your unique provider identifier +- `local_db_user`: Database username +- `local_db_password`: Database password +- `local_wallet_json`: Wallet JSON (either direct or via file) + +Optional variables (with defaults): +- `aws_region`: AWS region (default: us-east-1) +- `db_name`: Database name (default: orchestrator_db) +- `secrets_prefix`: Prefix for secrets (default: /orchestrator) The Database configuration is highly suggested and the secrets can be left alone as its just the name of the secrets @@ -127,7 +136,9 @@ ``` Type `yes` when prompted to confirm. -This setup ensures your node is deployed with the highest uptime and optimal performance. +This setup ensures your node is deployed with the highest uptime and optimal performance. + +Please open up the AWS console and show the logs of this to Ethan top receive the Tokens to stake # Terraform Configuration for Randomness Provider @@ -138,42 +149,6 @@ This Terraform configuration sets up: - VDF Fargate spot job configuration - AWS Secrets Manager for sensitive data -## Setup Instructions - -1. Copy the example variables file: -```bash -cp terraform.tfvars.example terraform.tfvars -``` - -2. Edit `terraform.tfvars` with your configuration: - - Set your database credentials - - Set your provider ID - - Copy your wallet JSON and paste it into the `local_wallet_json` variable - -Example wallet JSON format: -```json -{ - "address": "your-wallet-address", - "privateKey": "your-private-key" -} -``` - -Note: The wallet JSON should be pasted directly into the terraform.tfvars file - -3. Initialize Terraform: -```bash -terraform init -``` - -4. Review the planned changes: -```bash -terraform plan -``` - -5. Apply the configuration: -```bash -terraform apply -``` ## Security Notes @@ -190,18 +165,6 @@ terraform apply - **IAM**: Roles and policies for ECS tasks and secrets access - **CloudWatch**: Log groups for monitoring -## Variables Reference - -Required variables: -- `provider_id`: Your unique provider identifier -- `local_db_user`: Database username -- `local_db_password`: Database password -- `local_wallet_json`: Wallet JSON (either direct or via file) - -Optional variables (with defaults): -- `aws_region`: AWS region (default: us-east-1) -- `db_name`: Database name (default: orchestrator_db) -- `secrets_prefix`: Prefix for secrets (default: /orchestrator) [Main docs](../README.md) \ No newline at end of file From d72b3ed53b873153c08f4f8fc1f25d899c4c9a15 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 3 Feb 2025 09:32:09 -0500 Subject: [PATCH 22/51] image fix hotfix --- docker-compose/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index b002fb5..d39b66c 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.8' services: postgres: - image: postgres:13 + image: postgres:13-alpine environment: POSTGRES_USER: ${DB_USER:-myuser} POSTGRES_PASSWORD: ${DB_PASSWORD:-mypassword} From 350e4e63b0b501bf9e27e042cef36ee737f09927 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 3 Feb 2025 09:51:39 -0500 Subject: [PATCH 23/51] added arch --- orchestrator/Dockerfile | 18 +++++++++--------- orchestrator/docs/development.md | 26 +++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/orchestrator/Dockerfile b/orchestrator/Dockerfile index e0b8740..0ce3931 100644 --- a/orchestrator/Dockerfile +++ b/orchestrator/Dockerfile @@ -1,23 +1,23 @@ -# Use the official Node.js image as the base image -FROM node:20 +# Use the official lightweight Node.js image +FROM node:20-bullseye-slim -# Create and set the working directory +# Create a working directory WORKDIR /usr/src/app -# Copy package.json and package-lock.json +# Copy package files first to leverage Docker layer caching COPY package*.json ./ -# Install dependencies +# Install dependencies including dev dependencies (TypeScript) RUN npm install -# Copy the entire project into the container +# Copy the source files COPY . . -# Compile TypeScript to JavaScript +# Compile TypeScript code RUN npx tsc -# Expose the port if needed +# Expose the app port EXPOSE 3000 -# Run the compiled app +# Run the app CMD ["node", "dist/app.js"] diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index 35158c0..fe879a7 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -6,4 +6,28 @@ docker build -t randao/orchestrator:latest -t randao/orchestrator:v0.1.10 . docker inspect -f '{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -q) -npx ts-node src/clear_outputs.ts \ No newline at end of file +npx ts-node src/clear_outputs.ts + + + + + + + + +docker build -t randao/orchestrator:latest -t randao/orchestrator:v0.2.50 . + +docker login + +docker push randao/orchestrator:latest +docker push randao/orchestrator:v0.2.50 + + +docker buildx create --use +docker buildx inspect --bootstrap + + +docker buildx build --platform linux/amd64,linux/arm64 \ +-t randao/orchestrator:latest \ +-t randao/orchestrator:v0.2.50 \ +--push . From b33b88d489eded9714b8c6d7e121f9bcd66f654e Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 3 Feb 2025 10:58:59 -0500 Subject: [PATCH 24/51] added pulling image --- orchestrator/docs/development.md | 16 +++++--- orchestrator/src/app.ts | 65 ++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index fe879a7..131f9cf 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -15,19 +15,25 @@ npx ts-node src/clear_outputs.ts -docker build -t randao/orchestrator:latest -t randao/orchestrator:v0.2.50 . +# Export version as an environment variable +export VERSION=v0.2.51 # You can change this value to any version you want +# Build the Docker image with the version tag +docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . + +# Log in to Docker docker login +# Push the image with the version tag docker push randao/orchestrator:latest -docker push randao/orchestrator:v0.2.50 - +docker push randao/orchestrator:$VERSION +# Create and use buildx builder docker buildx create --use docker buildx inspect --bootstrap - +# Build the multi-platform image and push it docker buildx build --platform linux/amd64,linux/arm64 \ -t randao/orchestrator:latest \ --t randao/orchestrator:v0.2.50 \ +-t randao/orchestrator:$VERSION \ --push . diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 9072c4b..44abce0 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -46,6 +46,7 @@ let PreviousTotalAvailableRandom = 0; // Global variables to track polling status let pollingInProgress = false; let lastPollingId: string | null = null; +let pulledDockerimage = false; // Cache for network configuration let cachedNetworkConfig: NetworkConfig | null = null; @@ -149,6 +150,20 @@ async function triggerVDFJobPod(): Promise { const containerName = `vdf_job_${Date.now()}_${Math.floor(Math.random() * 100000)}`; console.log(`Starting Docker container with name: ${containerName}`); try { + if (!pulledDockerimage) { + console.log(`Pulling image: ${VDF_JOB_IMAGE}`); + await new Promise((resolve, reject) => { + docker.pull(VDF_JOB_IMAGE, (err: any, stream: NodeJS.ReadableStream) => { + if (err) { + return reject(err); + } + docker.modem.followProgress(stream, (doneErr) => { + if (doneErr) reject(doneErr); + else resolve(true); + }); + }); + }); + } const container = await docker.createContainer({ Image: VDF_JOB_IMAGE, Cmd: ['sh', '-c', `for i in $(seq 1 ${RANDOM_PER_VDF}); do python main.py; done`], @@ -291,38 +306,38 @@ function updateAvailableValuesAsync(currentCount: number) { async function getMoreRandom(currentCount: number) { const entriesNeeded = MINIMUM_ENTRIES - currentCount; - console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries...`); + console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries...`); - ongoingRequest = true; + ongoingRequest = true; - // Calculate how many containers to spawn - const possibleBatchCount = Math.ceil(entriesNeeded / RANDOM_PER_VDF); - const availableSpawns = Math.min( - possibleBatchCount, - MAX_OUTSTANDING_VDF_CONTAINERS - ongoingContainers.size - ); + // Calculate how many containers to spawn + const possibleBatchCount = Math.ceil(entriesNeeded / RANDOM_PER_VDF); + const availableSpawns = Math.min( + possibleBatchCount, + MAX_OUTSTANDING_VDF_CONTAINERS - ongoingContainers.size + ); - if (availableSpawns <= 0) { - console.log("Max outstanding containers reached. Skipping new container launches."); - ongoingRequest = false; - return; - } + if (availableSpawns <= 0) { + console.log("Max outstanding containers reached. Skipping new container launches."); + ongoingRequest = false; + return; + } - console.log(`Spawning up to ${availableSpawns} containers to generate random values.`); + console.log(`Spawning up to ${availableSpawns} containers to generate random values.`); - for (let i = 0; i < availableSpawns; i++) { - try { - const jobId = await triggerVDFJobPod(); - if (jobId) { - console.log(`Job triggered: ${jobId}`); - ongoingContainers.add(jobId); - } - } catch (error) { - console.error('Error triggering job pod:', error); + for (let i = 0; i < availableSpawns; i++) { + try { + const jobId = await triggerVDFJobPod(); + if (jobId) { + console.log(`Job triggered: ${jobId}`); + ongoingContainers.add(jobId); } + } catch (error) { + console.error('Error triggering job pod:', error); } + } } - + // Function to check and fetch database entries as needed async function checkAndFetchIfNeeded(client: Client) { @@ -361,7 +376,7 @@ async function checkAndFetchIfNeeded(client: Client) { // Check if more entries are needed if (currentCount >= MINIMUM_ENTRIES) return; getMoreRandom(currentCount) - + } catch (error) { console.error('Error during check and fetch:', error); } finally { From 59e4bc693d554f127be67c343f579549ee080993 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 3 Feb 2025 20:36:51 -0500 Subject: [PATCH 25/51] altered IAM policy --- terraform/ecs.tf | 2 +- terraform/iam.json | 87 ++++++++++++++++++++++++++++++ terraform/terraform.tfvars.example | 11 +++- 3 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 terraform/iam.json diff --git a/terraform/ecs.tf b/terraform/ecs.tf index 0a154fa..93efeb8 100644 --- a/terraform/ecs.tf +++ b/terraform/ecs.tf @@ -62,7 +62,7 @@ resource "aws_ecs_task_definition" "orchestrator_service" { container_definitions = jsonencode([ { name = "orchestrator" - image = "randao/orchestrator:v0.2.46" + image = "randao/orchestrator:v0.2.51" environment = [ { name = "ENVIRONMENT" diff --git a/terraform/iam.json b/terraform/iam.json new file mode 100644 index 0000000..1150854 --- /dev/null +++ b/terraform/iam.json @@ -0,0 +1,87 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecs:CreateCluster", + "ecs:DeleteCluster", + "ecs:CreateService", + "ecs:DeleteService", + "ecs:UpdateService", + "ecs:RegisterTaskDefinition", + "ecs:DeregisterTaskDefinition", + "ecs:ListTaskDefinitions", + "ecs:DescribeTaskDefinition", + "ecs:PutClusterCapacityProviders", + "ecs:DescribeClusters" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:CreateRole", + "iam:DeleteRole", + "iam:GetRole", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy", + "iam:PassRole" + ], + "Resource": "arn:aws:iam::*:role/orchestrator-*" + }, + { + "Effect": "Allow", + "Action": [ + "secretsmanager:CreateSecret", + "secretsmanager:DeleteSecret", + "secretsmanager:GetSecretValue", + "secretsmanager:PutSecretValue", + "secretsmanager:UpdateSecret", + "secretsmanager:TagResource" + ], + "Resource": "arn:aws:secretsmanager:*:*:secret:/orchestrator/*" + }, + { + "Effect": "Allow", + "Action": [ + "rds:CreateDBInstance", + "rds:DeleteDBInstance", + "rds:ModifyDBInstance", + "rds:DescribeDBInstances", + "rds:CreateDBSubnetGroup", + "rds:DeleteDBSubnetGroup", + "rds:ModifyDBSubnetGroup", + "rds:AddTagsToResource" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:DeleteLogGroup", + "logs:PutRetentionPolicy" + ], + "Resource": "arn:aws:logs:*:*:log-group:*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateSecurityGroup", + "ec2:DeleteSecurityGroup", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:CreateVpcEndpoint", + "ec2:DeleteVpcEndpoints", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeSecurityGroups", + "ec2:DescribeNetworkInterfaces", + "ec2:CreateTags" + ], + "Resource": "*" + } + ] +} diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example index 1c03b11..fcfa02b 100644 --- a/terraform/terraform.tfvars.example +++ b/terraform/terraform.tfvars.example @@ -13,8 +13,15 @@ db_name = "orchestrator_db" # Copy the contents of your wallet.json file and paste it here local_wallet_json = < Date: Mon, 3 Feb 2025 20:37:08 -0500 Subject: [PATCH 26/51] altered IAM policy --- terraform/README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/terraform/README.md b/terraform/README.md index d5d73b8..45e7540 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -20,7 +20,8 @@ "ecs:DeregisterTaskDefinition", "ecs:ListTaskDefinitions", "ecs:DescribeTaskDefinition", - "ecs:PutClusterCapacityProviders" + "ecs:PutClusterCapacityProviders", + "ecs:DescribeClusters" ], "Resource": "*" }, @@ -59,7 +60,8 @@ "rds:DescribeDBInstances", "rds:CreateDBSubnetGroup", "rds:DeleteDBSubnetGroup", - "rds:ModifyDBSubnetGroup" + "rds:ModifyDBSubnetGroup", + "rds:AddTagsToResource" ], "Resource": "*" }, @@ -70,7 +72,7 @@ "logs:DeleteLogGroup", "logs:PutRetentionPolicy" ], - "Resource": "arn:aws:logs:*:*:log-group:/ecs/*" + "Resource": "arn:aws:logs:*:*:log-group:*" }, { "Effect": "Allow", @@ -83,12 +85,14 @@ "ec2:DeleteVpcEndpoints", "ec2:DescribeVpcEndpoints", "ec2:DescribeSecurityGroups", - "ec2:DescribeNetworkInterfaces" + "ec2:DescribeNetworkInterfaces", + "ec2:CreateTags" ], "Resource": "*" } ] -}``` +} +``` Save the JSON and name it Click on Users and create a new user called TeraformDeployer Attach this new policy directly From 3dd7b1e272409e959beb2d3f11f269eac582cc1e Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 4 Feb 2025 17:28:11 -0500 Subject: [PATCH 27/51] modified terraform to leave secrets alone --- docker-compose/docker-compose.yml | 2 +- orchestrator/src/app.ts | 3 ++- terraform/secrets.tf | 13 +++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index d39b66c..1ef18ee 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -20,7 +20,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.2.46 + image: randao/orchestrator:v0.2.51 depends_on: postgres: condition: service_healthy diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 44abce0..af7fb33 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -306,6 +306,7 @@ function updateAvailableValuesAsync(currentCount: number) { async function getMoreRandom(currentCount: number) { const entriesNeeded = MINIMUM_ENTRIES - currentCount; + //TODO this math looked wrong in https://discord.com/channels/1209645894039896074/1333434937537204336/1336000074765045853 console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries...`); ongoingRequest = true; @@ -344,7 +345,7 @@ async function checkAndFetchIfNeeded(client: Client) { try { //Check if provider has been given a special signal const on_chain_avalible_random = await getProviderAvailableRandomValues(PROVIDER_ID); - + //TODO fix so if they are not staked to start it will detect and fix the onchain data and not leave it alone // Query current count of usable DB entries const res = await client.query( 'SELECT COUNT(*) AS count FROM verifiable_delay_functions WHERE request_id IS NULL' diff --git a/terraform/secrets.tf b/terraform/secrets.tf index d85170a..7a89ef4 100644 --- a/terraform/secrets.tf +++ b/terraform/secrets.tf @@ -4,10 +4,14 @@ resource "aws_secretsmanager_secret" "db_credentials" { name = "${var.secrets_prefix}/${var.db_credentials_secret_name}" description = "Database credentials for the orchestrator service" + + lifecycle { + ignore_changes = [name] + } } resource "aws_secretsmanager_secret_version" "db_credentials" { - secret_id = aws_secretsmanager_secret.db_credentials.id + secret_id = aws_secretsmanager_secret.db_credentials.id secret_string = jsonencode({ username = var.local_db_user password = var.local_db_password @@ -18,13 +22,18 @@ resource "aws_secretsmanager_secret_version" "db_credentials" { resource "aws_secretsmanager_secret" "wallet" { name = "${var.secrets_prefix}/${var.wallet_secret_name}" description = "Wallet JSON for the orchestrator service" + + lifecycle { + ignore_changes = [name] + } } resource "aws_secretsmanager_secret_version" "wallet" { - secret_id = aws_secretsmanager_secret.wallet.id + secret_id = aws_secretsmanager_secret.wallet.id secret_string = var.local_wallet_json } + # IAM policy for ECS tasks to access secrets data "aws_iam_policy_document" "secrets_access" { statement { From f167bb75465a42f7a8b54f8cb676073a8ff119df Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 7 Feb 2025 09:29:53 -0500 Subject: [PATCH 28/51] fixed bugs and staking --- orchestrator/docs/development.md | 2 +- orchestrator/src/app.ts | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index 131f9cf..46476e0 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.2.51 # You can change this value to any version you want +export VERSION=v0.2.55 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index af7fb33..0647f17 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -35,14 +35,12 @@ const RETRY_DELAY_MS = 10000; const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.4'; const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); -const ongoingTasks = new Set(); // Track task ARNs of running ECS tasks const ongoingContainers = new Set(); // Track container IDs of running Docker containers const PROVIDER_ID = process.env.PROVIDER_ID || "0"; const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "backend"; const COMPLETION_RETENTION_PERIOD_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds let ongoingRequest = false; let spotInterruptions = 0; -let PreviousTotalAvailableRandom = 0; // Global variables to track polling status let pollingInProgress = false; let lastPollingId: string | null = null; @@ -124,7 +122,7 @@ async function triggerVDFJobPod(): Promise { const taskArn = await launchVDFTask(ecs, cachedNetworkConfig, RANDOM_PER_VDF); if (taskArn) { - ongoingTasks.add(taskArn); + ongoingContainers.add(taskArn); console.log(`ECS task started successfully: ${taskArn}`); return taskArn; } @@ -195,11 +193,11 @@ async function triggerVDFJobPod(): Promise { // Modified function to wait for ECS tasks to complete and remove them from tracking async function monitorECSTasks(): Promise { - if (ongoingTasks.size === 0) return; + if (ongoingContainers.size === 0) return; const describeTasksResult = await ecs.describeTasks({ cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', - tasks: Array.from(ongoingTasks) + tasks: Array.from(ongoingContainers) }).promise(); describeTasksResult.tasks?.forEach(task => { @@ -219,7 +217,7 @@ async function monitorECSTasks(): Promise { } else { console.log('Task stopped due to normal completion.'); } - ongoingTasks.delete(task.taskArn as string); + ongoingContainers.delete(task.taskArn as string); } }); } @@ -345,7 +343,6 @@ async function checkAndFetchIfNeeded(client: Client) { try { //Check if provider has been given a special signal const on_chain_avalible_random = await getProviderAvailableRandomValues(PROVIDER_ID); - //TODO fix so if they are not staked to start it will detect and fix the onchain data and not leave it alone // Query current count of usable DB entries const res = await client.query( 'SELECT COUNT(*) AS count FROM verifiable_delay_functions WHERE request_id IS NULL' @@ -366,10 +363,9 @@ async function checkAndFetchIfNeeded(client: Client) { break; default: console.log("Value is not -1, -2, or -3"); - if (PreviousTotalAvailableRandom !== currentCount) { - console.log(`Updating available random values from ${PreviousTotalAvailableRandom} to ${currentCount}`); + if (on_chain_avalible_random.availibleRandomValues !== currentCount) { + console.log(`Updating available random values from ${on_chain_avalible_random.availibleRandomValues} to ${currentCount}`); updateAvailableValuesAsync(currentCount); - PreviousTotalAvailableRandom = currentCount; } } if (ongoingRequest) return; // Prevent redundant operations @@ -456,7 +452,7 @@ function getLogId(): string { return `[LogID: ${randomId} | ${timestamp}]`; } -async function getProviderRequests(PROVIDER_ID: string, parentLogId: string): Promise { +async function getProviderRequests(PROVIDER_ID: string, parentLogId: string): Promise { let openRequests: GetOpenRandomRequestsResponse; @@ -487,13 +483,15 @@ async function getProviderRequests(PROVIDER_ID: string, parentLogId: string): Pr openRequests = await fetchOpenRequests(); // Retry request } + if(openRequests.toString().includes("not found")){ + return false + } console.log(`${parentLogId} Step 1: Open Requests: ${JSON.stringify(openRequests)}`); console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${openRequests.activeChallengeRequests.request_ids.length}`); console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${openRequests.activeOutputRequests.request_ids.length}`); return openRequests; } -//Todo timeout the second anddefault to 0 async function getProviderAvailableRandomValues(PROVIDER_ID: string): Promise { let avalibleRandom: GetProviderAvailableValuesResponse; // Create a function to fetch open requests with a timeout @@ -552,6 +550,10 @@ async function polling(client: any) { const s1 = Date.now(); console.log(`${logId} Step 1 started.`); const openRequests = await getProviderRequests(PROVIDER_ID, logId); + if(openRequests == false){ + console.log("Provider is set up and ready. Please stake to join network at https://providers_randao.ar.io") + return + } stepTracking.step1 = { completed: true, timeTaken: Date.now() - s1 }; console.log(`${logId} Step 1: Open requests fetched. Time taken: ${stepTracking.step1.timeTaken}ms`); From a7e7e275ad908e9e77a47146a0074da8b0757cae Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 7 Feb 2025 10:06:03 -0500 Subject: [PATCH 29/51] fixed up some stuff --- docker-compose/dev-docker-compose.yml | 2 +- docker-compose/docker-compose.yml | 2 +- requester/package.json | 2 +- requester/src/app.ts | 100 ++++++++++++++++++++------ terraform/ecs.tf | 2 +- 5 files changed, 81 insertions(+), 27 deletions(-) diff --git a/docker-compose/dev-docker-compose.yml b/docker-compose/dev-docker-compose.yml index 5ed36c1..0069344 100644 --- a/docker-compose/dev-docker-compose.yml +++ b/docker-compose/dev-docker-compose.yml @@ -133,7 +133,7 @@ services: - dbeaver-data:/opt/cloudbeaver/workspace requester: - image: randao/requester:v0.2.1 + image: randao/requester:v0.2.7 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 1ef18ee..a9bcffa 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -20,7 +20,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.2.51 + image: randao/orchestrator:v0.2.55 depends_on: postgres: condition: service_healthy diff --git a/requester/package.json b/requester/package.json index 798686b..a2e12d5 100644 --- a/requester/package.json +++ b/requester/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "3.5.17", + "ao-process-clients": "3.7.3", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/requester/src/app.ts b/requester/src/app.ts index 142f2d2..673c16c 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -3,18 +3,17 @@ import { IRandomClient, RandomClient, RandomClientConfig, + StakingClient, } from "ao-process-clients"; -const PROVIDER_IDS = [ - "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE", - "c8Iq4yunDnsJWGSz_wYwQU--O9qeODKHiRdUkQkW2p8", - "Sr3HVH0Nh6iZzbORLpoQFOEvmsuKjXsHswSWH760KAk", - "1zlA7nKecUGevGNAEbjim_SlbioOI6daNNn2luDEHb0" -]; - -const RETRY_DELAY_MS = 5000; //5 seconds +const RETRY_DELAY_MS = 1000; // 1 second +const PROVIDER_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes +const PROVIDER_REQUEST_TIMEOUT = 60 * 1000; // 1 minute const CHANCE_TO_CALL_RANDOM = 1; +let cachedProviders: string[] = []; +let lastProviderRefresh = 0; + const RANDOM_CONFIG: RandomClientConfig = { tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", processId: "yKVS1tYE3MajUpZqEIORmW1J8HTke-6o6o6tnlkFOZQ", @@ -27,23 +26,78 @@ let totalTimeToFulfill = 0; let fulfilledRequests = 0; const outstandingRequests: Set = new Set(); -function getRandomProviders(): { providers: string[], count: number } { - // Randomly select how many providers we want (1-3) - const count = Math.floor(Math.random() * 3) + 1; - - // Shuffle the provider array and take the first 'count' elements - const shuffled = [...PROVIDER_IDS] - .sort(() => Math.random() - 0.5) - .slice(0, Math.min(count, PROVIDER_IDS.length)); +async function getRandomProviders(stakeclient: StakingClient): Promise<{ providers: string[], count: number }> { + const now = Date.now(); - return { - providers: shuffled, - count: shuffled.length - }; + // If we have cached providers and they're not expired, use them + if (cachedProviders.length > 0 && (now - lastProviderRefresh) < PROVIDER_REFRESH_INTERVAL) { + const count = Math.floor(Math.random() * 3) + 1; + const shuffled = [...cachedProviders] + .sort(() => Math.random() - 0.5) + .slice(0, Math.min(count, cachedProviders.length)); + return { + providers: shuffled, + count: shuffled.length + }; + } + + try { + // Create a promise that rejects after timeout + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error("Provider request timed out")), PROVIDER_REQUEST_TIMEOUT); + }); + + // Create the actual provider fetch promise + const fetchPromise = async () => { + const providerInfo = await stakeclient.getAllProvidersInfo(); + const eligibleProviders = providerInfo + .filter(provider => provider.active === 1) + .map(provider => provider.provider_id); + + if (eligibleProviders.length === 0) { + throw new Error("No eligible providers found with active status"); + } + + // Update cache + cachedProviders = eligibleProviders; + lastProviderRefresh = now; + + const count = Math.floor(Math.random() * 3) + 1; + const shuffled = [...eligibleProviders] + .sort(() => Math.random() - 0.5) + .slice(0, Math.min(count, eligibleProviders.length)); + + return { + providers: shuffled, + count: shuffled.length + }; + }; + + // Race between timeout and fetch + return await Promise.race([fetchPromise(), timeoutPromise]); + } catch (error) { + console.error("Error fetching providers:", error); + + // If we have cached providers, use them as fallback + if (cachedProviders.length > 0) { + console.log("Using cached providers as fallback"); + const count = Math.floor(Math.random() * 3) + 1; + const shuffled = [...cachedProviders] + .sort(() => Math.random() - 0.5) + .slice(0, Math.min(count, cachedProviders.length)); + return { + providers: shuffled, + count: shuffled.length + }; + } + + throw error; // Re-throw if we have no fallback + } } async function main() { - const randclient: IRandomClient = new RandomClient(RANDOM_CONFIG); + const randclient = new RandomClient(RANDOM_CONFIG); + const stakeclient = new StakingClient(RANDOM_CONFIG); while (true) { console.log("Running") @@ -52,7 +106,7 @@ async function main() { if (Math.random() < CHANCE_TO_CALL_RANDOM) { console.log("Initiating random request..."); const callbackId = `callback-${Date.now()}`; - const { providers, count } = getRandomProviders(); + const { providers, count } = await getRandomProviders(stakeclient); console.log(`Selected ${count} providers:`, providers); await randclient.createRequest(providers, count, callbackId); totalRandomCalled++; @@ -118,4 +172,4 @@ function delay(ms: number): Promise { } // Call the main function -main(); \ No newline at end of file +main(); diff --git a/terraform/ecs.tf b/terraform/ecs.tf index 93efeb8..ed8e315 100644 --- a/terraform/ecs.tf +++ b/terraform/ecs.tf @@ -62,7 +62,7 @@ resource "aws_ecs_task_definition" "orchestrator_service" { container_definitions = jsonencode([ { name = "orchestrator" - image = "randao/orchestrator:v0.2.51" + image = "randao/orchestrator:v0.2.55" environment = [ { name = "ENVIRONMENT" From e7d8ca5a0f0b688e3b748ad23c6746121e26004d Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 26 Feb 2025 05:37:56 -0500 Subject: [PATCH 30/51] New code --- docker-compose/dev-docker-compose.yml | 6 +- docker-compose/docker-compose.yml | 6 +- docker-compose/mass-dev-docker-compose.yml | 645 +++++++++++++++++++++ orchestrator/docs/development.md | 2 +- orchestrator/package.json | 2 +- orchestrator/src/app.ts | 59 +- orchestrator/src/clear_outputs.ts | 26 +- orchestrator/src/stake.ts | 26 +- terraform/ecs.tf | 2 +- 9 files changed, 727 insertions(+), 47 deletions(-) create mode 100644 docker-compose/mass-dev-docker-compose.yml diff --git a/docker-compose/dev-docker-compose.yml b/docker-compose/dev-docker-compose.yml index 0069344..9789769 100644 --- a/docker-compose/dev-docker-compose.yml +++ b/docker-compose/dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.2.46 + image: randao/orchestrator:v0.2.80 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.2.46 + image: randao/orchestrator:v0.2.80 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.2.46 + image: randao/orchestrator:v0.2.80 depends_on: postgres3: condition: service_healthy diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index a9bcffa..e93ca33 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: postgres: image: postgres:13-alpine @@ -8,7 +6,7 @@ services: POSTGRES_PASSWORD: ${DB_PASSWORD:-mypassword} POSTGRES_DB: ${DB_NAME:-mydatabase} ports: - - "5432:5432" + - "5431:5432" networks: - backend volumes: @@ -20,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.2.55 + image: randao/orchestrator:v0.3.0 depends_on: postgres: condition: service_healthy diff --git a/docker-compose/mass-dev-docker-compose.yml b/docker-compose/mass-dev-docker-compose.yml new file mode 100644 index 0000000..7e5c4e1 --- /dev/null +++ b/docker-compose/mass-dev-docker-compose.yml @@ -0,0 +1,645 @@ +version: '3.8' + +services: + # Instance 1 + postgres1: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_1:-myuser1} + POSTGRES_PASSWORD: ${DB_PASSWORD_1:-mypassword1} + POSTGRES_DB: ${DB_NAME_1:-mydatabase1} + ports: + - "5432:5432" + networks: + - backend + volumes: + - pgdata1:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_1:-myuser1} -d ${DB_NAME_1:-mydatabase1}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator1: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres1: + condition: service_healthy + environment: + DB_HOST: postgres1 + DB_PORT: 5432 + DB_USER: ${DB_USER_1:-myuser1} + DB_PASSWORD: ${DB_PASSWORD_1:-mypassword1} + DB_NAME: ${DB_NAME_1:-mydatabase1} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_1} + PROVIDER_ID: ${PROVIDER_ID_1} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 2 + postgres2: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_2:-myuser2} + POSTGRES_PASSWORD: ${DB_PASSWORD_2:-mypassword2} + POSTGRES_DB: ${DB_NAME_2:-mydatabase2} + ports: + - "5433:5432" + networks: + - backend + volumes: + - pgdata2:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_2:-myuser2} -d ${DB_NAME_2:-mydatabase2}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator2: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres2: + condition: service_healthy + environment: + DB_HOST: postgres2 + DB_PORT: 5432 + DB_USER: ${DB_USER_2:-myuser2} + DB_PASSWORD: ${DB_PASSWORD_2:-mypassword2} + DB_NAME: ${DB_NAME_2:-mydatabase2} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_2} + PROVIDER_ID: ${PROVIDER_ID_2} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 3 + postgres3: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_3:-myuser3} + POSTGRES_PASSWORD: ${DB_PASSWORD_3:-mypassword3} + POSTGRES_DB: ${DB_NAME_3:-mydatabase3} + ports: + - "5434:5432" + networks: + - backend + volumes: + - pgdata3:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_3:-myuser3} -d ${DB_NAME_3:-mydatabase3}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator3: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres3: + condition: service_healthy + environment: + DB_HOST: postgres3 + DB_PORT: 5432 + DB_USER: ${DB_USER_3:-myuser3} + DB_PASSWORD: ${DB_PASSWORD_3:-mypassword3} + DB_NAME: ${DB_NAME_3:-mydatabase3} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_3} + PROVIDER_ID: ${PROVIDER_ID_3} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 4 + postgres4: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_4:-myuser4} + POSTGRES_PASSWORD: ${DB_PASSWORD_4:-mypassword4} + POSTGRES_DB: ${DB_NAME_4:-mydatabase4} + ports: + - "5435:5432" + networks: + - backend + volumes: + - pgdata4:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_4:-myuser4} -d ${DB_NAME_4:-mydatabase4}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator4: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres4: + condition: service_healthy + environment: + DB_HOST: postgres4 + DB_PORT: 5432 + DB_USER: ${DB_USER_4:-myuser4} + DB_PASSWORD: ${DB_PASSWORD_4:-mypassword4} + DB_NAME: ${DB_NAME_4:-mydatabase4} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_4} + PROVIDER_ID: ${PROVIDER_ID_4} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 5 + postgres5: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_5:-myuser5} + POSTGRES_PASSWORD: ${DB_PASSWORD_5:-mypassword5} + POSTGRES_DB: ${DB_NAME_5:-mydatabase5} + ports: + - "5436:5432" + networks: + - backend + volumes: + - pgdata5:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_5:-myuser5} -d ${DB_NAME_5:-mydatabase5}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator5: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres5: + condition: service_healthy + environment: + DB_HOST: postgres5 + DB_PORT: 5432 + DB_USER: ${DB_USER_5:-myuser5} + DB_PASSWORD: ${DB_PASSWORD_5:-mypassword5} + DB_NAME: ${DB_NAME_5:-mydatabase5} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_5} + PROVIDER_ID: ${PROVIDER_ID_5} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 6 + postgres6: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_6:-myuser6} + POSTGRES_PASSWORD: ${DB_PASSWORD_6:-mypassword6} + POSTGRES_DB: ${DB_NAME_6:-mydatabase6} + ports: + - "5437:5432" + networks: + - backend + volumes: + - pgdata6:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_6:-myuser6} -d ${DB_NAME_6:-mydatabase6}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator6: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres6: + condition: service_healthy + environment: + DB_HOST: postgres6 + DB_PORT: 5432 + DB_USER: ${DB_USER_6:-myuser6} + DB_PASSWORD: ${DB_PASSWORD_6:-mypassword6} + DB_NAME: ${DB_NAME_6:-mydatabase6} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_6} + PROVIDER_ID: ${PROVIDER_ID_6} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 7 + postgres7: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_7:-myuser7} + POSTGRES_PASSWORD: ${DB_PASSWORD_7:-mypassword7} + POSTGRES_DB: ${DB_NAME_7:-mydatabase7} + ports: + - "5438:5432" + networks: + - backend + volumes: + - pgdata7:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_7:-myuser7} -d ${DB_NAME_7:-mydatabase7}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator7: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres7: + condition: service_healthy + environment: + DB_HOST: postgres7 + DB_PORT: 5432 + DB_USER: ${DB_USER_7:-myuser7} + DB_PASSWORD: ${DB_PASSWORD_7:-mypassword7} + DB_NAME: ${DB_NAME_7:-mydatabase7} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_7} + PROVIDER_ID: ${PROVIDER_ID_7} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 8 + postgres8: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_8:-myuser8} + POSTGRES_PASSWORD: ${DB_PASSWORD_8:-mypassword8} + POSTGRES_DB: ${DB_NAME_8:-mydatabase8} + ports: + - "5439:5432" + networks: + - backend + volumes: + - pgdata8:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_8:-myuser8} -d ${DB_NAME_8:-mydatabase8}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator8: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres8: + condition: service_healthy + environment: + DB_HOST: postgres8 + DB_PORT: 5432 + DB_USER: ${DB_USER_8:-myuser8} + DB_PASSWORD: ${DB_PASSWORD_8:-mypassword8} + DB_NAME: ${DB_NAME_8:-mydatabase8} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_8} + PROVIDER_ID: ${PROVIDER_ID_8} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 9 + postgres9: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_9:-myuser9} + POSTGRES_PASSWORD: ${DB_PASSWORD_9:-mypassword9} + POSTGRES_DB: ${DB_NAME_9:-mydatabase9} + ports: + - "5440:5432" + networks: + - backend + volumes: + - pgdata9:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_9:-myuser9} -d ${DB_NAME_9:-mydatabase9}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator9: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres9: + condition: service_healthy + environment: + DB_HOST: postgres9 + DB_PORT: 5432 + DB_USER: ${DB_USER_9:-myuser9} + DB_PASSWORD: ${DB_PASSWORD_9:-mypassword9} + DB_NAME: ${DB_NAME_9:-mydatabase9} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_9} + PROVIDER_ID: ${PROVIDER_ID_9} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 10 + postgres10: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_10:-myuser10} + POSTGRES_PASSWORD: ${DB_PASSWORD_10:-mypassword10} + POSTGRES_DB: ${DB_NAME_10:-mydatabase10} + ports: + - "5441:5432" + networks: + - backend + volumes: + - pgdata10:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_10:-myuser10} -d ${DB_NAME_10:-mydatabase10}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator10: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres10: + condition: service_healthy + environment: + DB_HOST: postgres10 + DB_PORT: 5432 + DB_USER: ${DB_USER_10:-myuser10} + DB_PASSWORD: ${DB_PASSWORD_10:-mypassword10} + DB_NAME: ${DB_NAME_10:-mydatabase10} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_10} + PROVIDER_ID: ${PROVIDER_ID_10} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 11 + postgres11: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_11:-myuser11} + POSTGRES_PASSWORD: ${DB_PASSWORD_11:-mypassword11} + POSTGRES_DB: ${DB_NAME_11:-mydatabase11} + ports: + - "5442:5432" + networks: + - backend + volumes: + - pgdata11:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_11:-myuser11} -d ${DB_NAME_11:-mydatabase11}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator11: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres11: + condition: service_healthy + environment: + DB_HOST: postgres11 + DB_PORT: 5432 + DB_USER: ${DB_USER_11:-myuser11} + DB_PASSWORD: ${DB_PASSWORD_11:-mypassword11} + DB_NAME: ${DB_NAME_11:-mydatabase11} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_11} + PROVIDER_ID: ${PROVIDER_ID_11} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 12 + postgres12: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_12:-myuser12} + POSTGRES_PASSWORD: ${DB_PASSWORD_12:-mypassword12} + POSTGRES_DB: ${DB_NAME_12:-mydatabase12} + ports: + - "5443:5432" + networks: + - backend + volumes: + - pgdata12:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_12:-myuser12} -d ${DB_NAME_12:-mydatabase12}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator12: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres12: + condition: service_healthy + environment: + DB_HOST: postgres12 + DB_PORT: 5432 + DB_USER: ${DB_USER_12:-myuser12} + DB_PASSWORD: ${DB_PASSWORD_12:-mypassword12} + DB_NAME: ${DB_NAME_12:-mydatabase12} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_12} + PROVIDER_ID: ${PROVIDER_ID_12} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 13 + postgres13: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_13:-myuser13} + POSTGRES_PASSWORD: ${DB_PASSWORD_13:-mypassword13} + POSTGRES_DB: ${DB_NAME_13:-mydatabase13} + ports: + - "5444:5432" + networks: + - backend + volumes: + - pgdata13:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_13:-myuser13} -d ${DB_NAME_13:-mydatabase13}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator13: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres13: + condition: service_healthy + environment: + DB_HOST: postgres13 + DB_PORT: 5432 + DB_USER: ${DB_USER_13:-myuser13} + DB_PASSWORD: ${DB_PASSWORD_13:-mypassword13} + DB_NAME: ${DB_NAME_13:-mydatabase13} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_13} + PROVIDER_ID: ${PROVIDER_ID_13} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 14 + postgres14: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_14:-myuser14} + POSTGRES_PASSWORD: ${DB_PASSWORD_14:-mypassword14} + POSTGRES_DB: ${DB_NAME_14:-mydatabase14} + ports: + - "5445:5432" + networks: + - backend + volumes: + - pgdata14:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_14:-myuser14} -d ${DB_NAME_14:-mydatabase14}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator14: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres14: + condition: service_healthy + environment: + DB_HOST: postgres14 + DB_PORT: 5432 + DB_USER: ${DB_USER_14:-myuser14} + DB_PASSWORD: ${DB_PASSWORD_14:-mypassword14} + DB_NAME: ${DB_NAME_14:-mydatabase14} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_14} + PROVIDER_ID: ${PROVIDER_ID_14} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Instance 15 + postgres15: + image: postgres:13 + environment: + POSTGRES_USER: ${DB_USER_15:-myuser15} + POSTGRES_PASSWORD: ${DB_PASSWORD_15:-mypassword15} + POSTGRES_DB: ${DB_NAME_15:-mydatabase15} + ports: + - "5446:5432" + networks: + - backend + volumes: + - pgdata15:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER_15:-myuser15} -d ${DB_NAME_15:-mydatabase15}"] + interval: 10s + timeout: 5s + retries: 5 + + orchestrator15: + image: randao/orchestrator:v0.2.80 + depends_on: + postgres15: + condition: service_healthy + environment: + DB_HOST: postgres15 + DB_PORT: 5432 + DB_USER: ${DB_USER_15:-myuser15} + DB_PASSWORD: ${DB_PASSWORD_15:-mypassword15} + DB_NAME: ${DB_NAME_15:-mydatabase15} + ENVIRONMENT: local + WALLET_JSON: ${WALLET_JSON_15} + PROVIDER_ID: ${PROVIDER_ID_15} + DOCKER_NETWORK: backend + networks: + - backend + volumes: + - /var/run/docker.sock:/var/run/docker.sock + + # Single DBeaver Instance + dbeaver: + image: dbeaver/cloudbeaver:23.2.0 + environment: + CB_SERVER_SERVER_PORT: 8080 + CB_SERVER_ADMIN_NAME: "admin" + CB_SERVER_ADMIN_PASSWORD: "admin123" + networks: + - backend + ports: + - "8080:8978" + volumes: + - dbeaver-data:/opt/cloudbeaver/workspace + + requester: + image: randao/requester:v0.2.7 + environment: + REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} + +networks: + backend: + name: backend + driver: bridge + +volumes: + pgdata1: + driver: local + pgdata2: + driver: local + pgdata3: + driver: local + pgdata4: + driver: local + pgdata5: + driver: local + pgdata6: + driver: local + pgdata7: + driver: local + pgdata8: + driver: local + pgdata9: + driver: local + pgdata10: + driver: local + pgdata11: + driver: local + pgdata12: + driver: local + pgdata13: + driver: local + pgdata14: + driver: local + pgdata15: + driver: local + dbeaver-data: + driver: local diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index 46476e0..411b276 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.2.55 # You can change this value to any version you want +export VERSION=v0.3.0 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/package.json b/orchestrator/package.json index 959d2ce..34df87e 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients":"3.6.0", + "ao-process-clients":"5.2.8", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 0647f17..ad37d28 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -1,26 +1,44 @@ import { Client } from 'pg'; import Docker from 'dockerode'; import AWS from 'aws-sdk'; -import { Environment, GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomClientConfig } from "ao-process-clients" +import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, RandomClient, RandomClientConfig} from "ao-process-clients" import { dbConfig } from './db_config.js'; import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; +// const RANDOM_CONFIG: RandomClientConfig = { +// wallet: JSON.parse(process.env.WALLET_JSON!), +// tokenProcessId: '', +// processId: '' +// } +//const randclient: IRandomClient = RandomClient.autoConfiguration() +async function getRandomClient(): Promise{ +// let test = await getRandomClientAutoConfiguration() +// test.wallet = JSON.parse(process.env.WALLET_JSON!) + const RANDOM_CONFIG: RandomClientConfig = { - tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", - processId: "yKVS1tYE3MajUpZqEIORmW1J8HTke-6o6o6tnlkFOZQ", wallet: JSON.parse(process.env.WALLET_JSON!), - environment: 'mainnet' + tokenProcessId: '5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI', + processId: '1dnDvaDRQ7Ao6o1ohTr7NNrN5mp1CpsXFrWm3JJFEs8' } -//const randclient: IRandomClient = RandomClient.autoConfiguration() const randclient = new RandomClient(RANDOM_CONFIG) + return randclient +} + +// async function getStakingClient(): Promise{ +// let test = await getProviderStakingClientAutoConfiguration() +// test.wallet = JSON.parse(process.env.WALLET_JSON!) +// const randclient = new ProviderStakingClient(test) +// return randclient +// } + const docker = new Docker(); // Constants for configuration -const POLLING_INTERVAL_MS = 5000; +const POLLING_INTERVAL_MS = 1000; const MINIMUM_ENTRIES = 500; const DRYRUNTIMEOUT = 15000; // 15 seconds const DRYRUNRESETTIME = 300000; // 5 min @@ -161,6 +179,7 @@ async function triggerVDFJobPod(): Promise { }); }); }); + pulledDockerimage = true; } const container = await docker.createContainer({ Image: VDF_JOB_IMAGE, @@ -294,7 +313,7 @@ function addHexPrefix(value: string): string { function updateAvailableValuesAsync(currentCount: number) { return (async () => { try { - await randclient.updateProviderAvailableValues(currentCount); + (await getRandomClient()).updateProviderAvailableValues(currentCount); console.log(`Updated provider values to ${currentCount}`); } catch (error) { console.error("Failed to update provider values:", error); @@ -354,6 +373,7 @@ async function checkAndFetchIfNeeded(client: Client) { case -1: console.log("Provider is shutting down"); //TODO prepare for shutdown + //TODO the async causes it to ovewrite itself break; case -2: console.log("Value is -2"); @@ -363,7 +383,8 @@ async function checkAndFetchIfNeeded(client: Client) { break; default: console.log("Value is not -1, -2, or -3"); - if (on_chain_avalible_random.availibleRandomValues !== currentCount) { + console.log("Value is "+ on_chain_avalible_random.availibleRandomValues) + if (on_chain_avalible_random.availibleRandomValues != currentCount) { console.log(`Updating available random values from ${on_chain_avalible_random.availibleRandomValues} to ${currentCount}`); updateAvailableValuesAsync(currentCount); } @@ -406,7 +427,7 @@ async function fulfillRandomChallenge(client: Client, requestId: string, parentL const hexInput = addHexPrefix(input); console.log(`${parentLogId} Posting VDF challenge for Request ID: ${requestId}, DB ID: ${dbId}`); - await randclient.postVDFChallenge(requestId, hexModulus, hexInput); + await (await getRandomClient()).postVDFChallenge(requestId, hexModulus, hexInput); console.log(`${parentLogId} Challenge posted for Request ID: ${requestId}. Waiting to post proof...`); } catch (error) { console.error(`${parentLogId} Error posting VDF challenge for Request ID: ${requestId}:`, error); @@ -439,7 +460,7 @@ async function fulfillRandomOutput(client: Client, requestId: string, parentLogI const proofString = JSON.stringify(processedProof); console.log(`${parentLogId} Posting VDF output and proof for - ID: ${dbId}, request ID: ${requestId}`); - await randclient.postVDFOutputAndProof(requestId, processedOutput, proofString); + await (await getRandomClient()).postVDFOutputAndProof(requestId, processedOutput, proofString); console.log(`${parentLogId} Proof posted for request ID: ${requestId}`); } catch (error) { console.error(`${parentLogId} Error fulfilling random output for request ID: ${requestId}:`, error); @@ -459,7 +480,7 @@ async function getProviderRequests(PROVIDER_ID: string, parentLogId: string): Pr // Create a function to fetch open requests with a timeout const fetchOpenRequests = async (): Promise => { try { - const response = await randclient.getOpenRandomRequests(PROVIDER_ID); + const response = await (await getRandomClient()).getOpenRandomRequests(PROVIDER_ID); return response; } catch (error) { console.error(`${parentLogId} Error fetching requests: ${error}`); @@ -478,7 +499,8 @@ async function getProviderRequests(PROVIDER_ID: string, parentLogId: string): Pr ]); } catch (error) { console.log(`${parentLogId} Step 1: ${error}`); - randclient.setDryRunAsMessage(true); + //randclient.setDryRunAsMessage(true); + console.log("Removed this as its spamming") console.log("Switching dryrun off"); openRequests = await fetchOpenRequests(); // Retry request } @@ -497,7 +519,7 @@ async function getProviderAvailableRandomValues(PROVIDER_ID: string): Promise => { try { - const response = await randclient.getProviderAvailableValues(PROVIDER_ID); + const response = await (await getRandomClient()).getProviderAvailableValues(PROVIDER_ID); return response; } catch (error) { console.error(`Error fetching avalible random: ${error}`); @@ -516,7 +538,8 @@ async function getProviderAvailableRandomValues(PROVIDER_ID: string): Promise { await polling(client); }, POLLING_INTERVAL_MS); - setInterval(async () => { - randclient.setDryRunAsMessage(false); - console.log("Switching dryrun on") - }, DRYRUNRESETTIME); + // setInterval(async () => { + // randclient.setDryRunAsMessage(false); + // console.log("Switching dryrun on") + // }, DRYRUNRESETTIME); process.on("SIGTERM", async () => { diff --git a/orchestrator/src/clear_outputs.ts b/orchestrator/src/clear_outputs.ts index a554e4d..88772fc 100644 --- a/orchestrator/src/clear_outputs.ts +++ b/orchestrator/src/clear_outputs.ts @@ -1,16 +1,20 @@ import { Client } from 'pg'; -import { getRandomClientAutoConfiguration, RandomClient, RandomClientConfig } from "ao-process-clients"; +import { RandomClient, RandomClientConfig } from "ao-process-clients"; import { dbConfig } from './db_config'; // Random Client Configuration -const RANDOM_CONFIG: RandomClientConfig = { - tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", - processId: "yKVS1tYE3MajUpZqEIORmW1J8HTke-6o6o6tnlkFOZQ", - wallet: JSON.parse(process.env.WALLET_JSON!), - environment: 'mainnet' -} - -const randclient = new RandomClient(RANDOM_CONFIG); +async function getRandomClient(): Promise{ + // let test = await getRandomClientAutoConfiguration() + // test.wallet = JSON.parse(process.env.WALLET_JSON!) + + const RANDOM_CONFIG: RandomClientConfig = { + wallet: JSON.parse(process.env.WALLET_JSON!), + tokenProcessId: '5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI', + processId: '1dnDvaDRQ7Ao6o1ohTr7NNrN5mp1CpsXFrWm3JJFEs8' + } + const randclient = new RandomClient(RANDOM_CONFIG) + return randclient + } const PROVIDER_ID = process.env.PROVIDER_ID || "0"; // Function to connect to PostgreSQL @@ -25,7 +29,7 @@ async function connectToDatabase() { async function clearAllOutputRequests(client: Client) { try { console.log("Fetching open output requests..."); - const openRequests = await randclient.getOpenRandomRequests(PROVIDER_ID); + const openRequests = await (await getRandomClient()).getOpenRandomRequests(PROVIDER_ID); if (openRequests && openRequests.activeOutputRequests) { console.log(`Found ${openRequests.activeOutputRequests.request_ids.length} output requests to clear.`); @@ -34,7 +38,7 @@ async function clearAllOutputRequests(client: Client) { const clearPromises = openRequests.activeOutputRequests.request_ids.map(async (requestId: string) => { console.log(`Sending "No data" for output request ID: ${requestId}`); try { - await randclient.postVDFOutputAndProof(requestId, "No data", "No data"); + await (await getRandomClient()).postVDFOutputAndProof(requestId, "No data", "No data"); console.log(`"No data" successfully sent for request ID: ${requestId}`); } catch (error) { console.error(`Error sending "No data" for request ID: ${requestId}:`, error); diff --git a/orchestrator/src/stake.ts b/orchestrator/src/stake.ts index 9ed352f..862b81c 100644 --- a/orchestrator/src/stake.ts +++ b/orchestrator/src/stake.ts @@ -1,15 +1,25 @@ -import { getRandomClientAutoConfiguration, RandomClient, RandomClientConfig, StakingClient, } from "ao-process-clients"; -import { ProviderDetails } from "ao-process-clients/dist/src/clients/staking/abstract/types"; +import { ProviderDetails, ProviderStakingClient, StakingClientConfig } from "ao-process-clients"; // Random Client Configuration -const RANDOM_CONFIG: RandomClientConfig = { - tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", - processId: "yKVS1tYE3MajUpZqEIORmW1J8HTke-6o6o6tnlkFOZQ", +// async function getStakingClient(): Promise{ +// let test = await getProviderStakingClientAutoConfiguration() +// test.wallet = JSON.parse(process.env.WALLET_JSON!) +// const randclient = new ProviderStakingClient(test) +// return randclient +// } + +async function getStakingClient(): Promise{ +// let test = await getProviderStakingClientAutoConfiguration() +// test.wallet = JSON.parse(process.env.WALLET_JSON!) +const RANDOM_CONFIG: StakingClientConfig = { wallet: JSON.parse(process.env.WALLET_JSON!), - environment: 'mainnet' + tokenProcessId: '5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI', + processId: 'EIQJoqVWonlxsEe8xGpQZhh54wrmgE3q0tAsVIhKYQU' +} +const randclient = new ProviderStakingClient(RANDOM_CONFIG) + return randclient } -const randclient = new StakingClient(RANDOM_CONFIG); // Function to clear all output requests async function stake() { @@ -26,7 +36,7 @@ async function stake() { discord: "test_discord", /** Optional Telegram handle */ telegram: "test_tg"}; - console.log(await randclient.stake("100000000000000000000",providerDetails)) + console.log(await (await getStakingClient()).stakeWithDetails("100000000000000000000",providerDetails)) } catch (error) { console.error("An error occurred while staking:", error); } finally { diff --git a/terraform/ecs.tf b/terraform/ecs.tf index ed8e315..c57785a 100644 --- a/terraform/ecs.tf +++ b/terraform/ecs.tf @@ -62,7 +62,7 @@ resource "aws_ecs_task_definition" "orchestrator_service" { container_definitions = jsonencode([ { name = "orchestrator" - image = "randao/orchestrator:v0.2.55" + image = "randao/orchestrator:v0.3.0" environment = [ { name = "ENVIRONMENT" From f24dd1661dd8b0d77bce50a043eaa27985635f01 Mon Sep 17 00:00:00 2001 From: tendiehelpe Date: Thu, 27 Feb 2025 11:58:12 -0500 Subject: [PATCH 31/51] provider upgrade --- docker-compose/docker-compose.yml | 2 +- docker-compose/mass-dev-docker-compose.yml | 30 +++++++++++----------- orchestrator/docs/development.md | 2 +- orchestrator/package.json | 4 ++- orchestrator/src/app.ts | 27 ++++++++++++++++--- requester/package.json | 3 ++- terraform/ecs.tf | 2 +- 7 files changed, 47 insertions(+), 23 deletions(-) diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index e93ca33..3d7b40c 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.3.0 + image: randao/orchestrator:v0.3.2 depends_on: postgres: condition: service_healthy diff --git a/docker-compose/mass-dev-docker-compose.yml b/docker-compose/mass-dev-docker-compose.yml index 7e5c4e1..2da3ea1 100644 --- a/docker-compose/mass-dev-docker-compose.yml +++ b/docker-compose/mass-dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres3: condition: service_healthy @@ -138,7 +138,7 @@ services: retries: 5 orchestrator4: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres4: condition: service_healthy @@ -177,7 +177,7 @@ services: retries: 5 orchestrator5: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres5: condition: service_healthy @@ -216,7 +216,7 @@ services: retries: 5 orchestrator6: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres6: condition: service_healthy @@ -255,7 +255,7 @@ services: retries: 5 orchestrator7: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres7: condition: service_healthy @@ -294,7 +294,7 @@ services: retries: 5 orchestrator8: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres8: condition: service_healthy @@ -333,7 +333,7 @@ services: retries: 5 orchestrator9: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres9: condition: service_healthy @@ -372,7 +372,7 @@ services: retries: 5 orchestrator10: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres10: condition: service_healthy @@ -411,7 +411,7 @@ services: retries: 5 orchestrator11: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres11: condition: service_healthy @@ -450,7 +450,7 @@ services: retries: 5 orchestrator12: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres12: condition: service_healthy @@ -489,7 +489,7 @@ services: retries: 5 orchestrator13: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres13: condition: service_healthy @@ -528,7 +528,7 @@ services: retries: 5 orchestrator14: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres14: condition: service_healthy @@ -567,7 +567,7 @@ services: retries: 5 orchestrator15: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.2 depends_on: postgres15: condition: service_healthy diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index 411b276..f2fb4ba 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.3.0 # You can change this value to any version you want +export VERSION=v0.3.1 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/package.json b/orchestrator/package.json index 34df87e..1d32dd4 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,9 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients":"5.2.8", + "ao-process-clients": "5.2.8", + "ao-vrf": "file:", + "arweave": "^1.15.5", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index ad37d28..18c191d 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -4,7 +4,7 @@ import AWS from 'aws-sdk'; import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, RandomClient, RandomClientConfig} from "ao-process-clients" import { dbConfig } from './db_config.js'; import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; - +import Arweave from 'arweave'; // const RANDOM_CONFIG: RandomClientConfig = { // wallet: JSON.parse(process.env.WALLET_JSON!), @@ -41,7 +41,7 @@ const docker = new Docker(); const POLLING_INTERVAL_MS = 1000; const MINIMUM_ENTRIES = 500; const DRYRUNTIMEOUT = 15000; // 15 seconds -const DRYRUNRESETTIME = 300000; // 5 min +//const DRYRUNRESETTIME = 300000; // 5 min //Expected increments per second=10×0.005=0.05 //180 times per hour //4,320 times per day @@ -54,7 +54,7 @@ const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.4'; const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); const ongoingContainers = new Set(); // Track container IDs of running Docker containers -const PROVIDER_ID = process.env.PROVIDER_ID || "0"; +let PROVIDER_ID = ""; const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "backend"; const COMPLETION_RETENTION_PERIOD_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds let ongoingRequest = false; @@ -68,6 +68,8 @@ let pulledDockerimage = false; let cachedNetworkConfig: NetworkConfig | null = null; +const arweave = Arweave.init({}); + interface StepTracking { step1?: { completed: boolean; timeTaken: number }; step2?: { completed: boolean; timeTaken: number }; @@ -321,6 +323,18 @@ function updateAvailableValuesAsync(currentCount: number) { })(); } +async function shutdown() { + return (async () => { + try { + let message = await (await getRandomClient()).updateProviderAvailableValues(0); + console.log(message) + console.log(`Updated provider values to ${0}`); + } catch (error) { + console.error("Failed to update provider values:", error); + } + })(); +} + async function getMoreRandom(currentCount: number) { const entriesNeeded = MINIMUM_ENTRIES - currentCount; //TODO this math looked wrong in https://discord.com/channels/1209645894039896074/1333434937537204336/1336000074765045853 @@ -810,6 +824,12 @@ async function run(): Promise { const client = await connectWithRetry(); await setupDatabase(client); + arweave.wallets.jwkToAddress(JSON.parse(process.env.WALLET_JSON!)).then((address) => { + console.log(address); + PROVIDER_ID = address + //1seRanklLU_1VTGkEk7P0xAwMJfA7owA1JHW5KyZKlY + }); + setInterval(async () => { const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); console.log(`Periodic log - Current database size: ${res.rows[0].count}`); @@ -838,6 +858,7 @@ async function run(): Promise { process.on("SIGTERM", async () => { console.log("SIGTERM received. Closing database connection."); await client.end(); + await shutdown(); process.exit(0); }); } diff --git a/requester/package.json b/requester/package.json index a2e12d5..57d291a 100644 --- a/requester/package.json +++ b/requester/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "ao-process-clients": "3.7.3", + "ao-vrf": "file:", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "crypto": "^1.0.1", @@ -24,4 +25,4 @@ "keywords": [], "author": "", "license": "ISC" -} \ No newline at end of file +} diff --git a/terraform/ecs.tf b/terraform/ecs.tf index c57785a..53e78dd 100644 --- a/terraform/ecs.tf +++ b/terraform/ecs.tf @@ -62,7 +62,7 @@ resource "aws_ecs_task_definition" "orchestrator_service" { container_definitions = jsonencode([ { name = "orchestrator" - image = "randao/orchestrator:v0.3.0" + image = "randao/orchestrator:v0.3.2" environment = [ { name = "ENVIRONMENT" From 1a023c7325a84387251f7e3a961a3e67db843be4 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 14 Mar 2025 17:41:35 -0400 Subject: [PATCH 32/51] wip --- docker-compose/dev-docker-compose.yml | 8 +-- docker-compose/mass-dev-docker-compose.yml | 32 +++++------ orchestrator/docs/development.md | 2 +- orchestrator/package.json | 2 +- orchestrator/src/app.ts | 63 ++++++++++++---------- requester/package.json | 2 +- requester/src/app.ts | 32 ++++++----- 7 files changed, 78 insertions(+), 63 deletions(-) diff --git a/docker-compose/dev-docker-compose.yml b/docker-compose/dev-docker-compose.yml index 9789769..b8a0e10 100644 --- a/docker-compose/dev-docker-compose.yml +++ b/docker-compose/dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.3 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.3 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.2.80 + image: randao/orchestrator:v0.3.3 depends_on: postgres3: condition: service_healthy @@ -133,7 +133,7 @@ services: - dbeaver-data:/opt/cloudbeaver/workspace requester: - image: randao/requester:v0.2.7 + image: randao/requester:v0.2.8 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/docker-compose/mass-dev-docker-compose.yml b/docker-compose/mass-dev-docker-compose.yml index 2da3ea1..f5f27cf 100644 --- a/docker-compose/mass-dev-docker-compose.yml +++ b/docker-compose/mass-dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres3: condition: service_healthy @@ -138,7 +138,7 @@ services: retries: 5 orchestrator4: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres4: condition: service_healthy @@ -177,7 +177,7 @@ services: retries: 5 orchestrator5: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres5: condition: service_healthy @@ -216,7 +216,7 @@ services: retries: 5 orchestrator6: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres6: condition: service_healthy @@ -255,7 +255,7 @@ services: retries: 5 orchestrator7: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres7: condition: service_healthy @@ -294,7 +294,7 @@ services: retries: 5 orchestrator8: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres8: condition: service_healthy @@ -333,7 +333,7 @@ services: retries: 5 orchestrator9: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres9: condition: service_healthy @@ -372,7 +372,7 @@ services: retries: 5 orchestrator10: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres10: condition: service_healthy @@ -411,7 +411,7 @@ services: retries: 5 orchestrator11: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres11: condition: service_healthy @@ -450,7 +450,7 @@ services: retries: 5 orchestrator12: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres12: condition: service_healthy @@ -489,7 +489,7 @@ services: retries: 5 orchestrator13: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres13: condition: service_healthy @@ -528,7 +528,7 @@ services: retries: 5 orchestrator14: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres14: condition: service_healthy @@ -567,7 +567,7 @@ services: retries: 5 orchestrator15: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.30 depends_on: postgres15: condition: service_healthy @@ -601,7 +601,7 @@ services: - dbeaver-data:/opt/cloudbeaver/workspace requester: - image: randao/requester:v0.2.7 + image: randao/requester:v0.3.0 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index f2fb4ba..ccfaed3 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.3.1 # You can change this value to any version you want +export VERSION=v0.3.3 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/package.json b/orchestrator/package.json index 1d32dd4..a1b1c60 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "5.2.8", + "ao-process-clients": "5.3.8", "ao-vrf": "file:", "arweave": "^1.15.5", "aws-sdk": "^2.1692.0", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 18c191d..99ed13c 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -1,7 +1,7 @@ import { Client } from 'pg'; import Docker from 'dockerode'; import AWS from 'aws-sdk'; -import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, RandomClient, RandomClientConfig} from "ao-process-clients" +import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, RandomClient, RandomClientConfig, RandomClientConfigBuilder} from "ao-process-clients" import { dbConfig } from './db_config.js'; import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; import Arweave from 'arweave'; @@ -12,17 +12,18 @@ import Arweave from 'arweave'; // processId: '' // } //const randclient: IRandomClient = RandomClient.autoConfiguration() -async function getRandomClient(): Promise{ -// let test = await getRandomClientAutoConfiguration() -// test.wallet = JSON.parse(process.env.WALLET_JSON!) - -const RANDOM_CONFIG: RandomClientConfig = { - wallet: JSON.parse(process.env.WALLET_JSON!), - tokenProcessId: '5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI', - processId: '1dnDvaDRQ7Ao6o1ohTr7NNrN5mp1CpsXFrWm3JJFEs8' -} -const randclient = new RandomClient(RANDOM_CONFIG) - return randclient +let randomClientInstance: RandomClient | null = null; + +async function getRandomClient(): Promise { + if (!randomClientInstance) { + const RANDOM_CONFIG: RandomClientConfig = await new RandomClientConfigBuilder() + .withWallet(JSON.parse(process.env.WALLET_JSON!)) + .build(); + + randomClientInstance = new RandomClient(RANDOM_CONFIG); + } + + return randomClientInstance; } // async function getStakingClient(): Promise{ @@ -34,18 +35,10 @@ const randclient = new RandomClient(RANDOM_CONFIG) const docker = new Docker(); - - - // Constants for configuration const POLLING_INTERVAL_MS = 1000; -const MINIMUM_ENTRIES = 500; -const DRYRUNTIMEOUT = 15000; // 15 seconds -//const DRYRUNRESETTIME = 300000; // 5 min -//Expected increments per second=10×0.005=0.05 -//180 times per hour -//4,320 times per day -//1,576,800 times per year +const MINIMUM_ENTRIES = 1000; +const DRYRUNTIMEOUT = 30000; // 30 seconds const MAX_OUTSTANDING_VDF_CONTAINERS = 10; const RANDOM_PER_VDF = 10; const MAX_RETRIES = 10; @@ -57,13 +50,16 @@ const ongoingContainers = new Set(); // Track container IDs of running D let PROVIDER_ID = ""; const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "backend"; const COMPLETION_RETENTION_PERIOD_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds +const UNCHAIN_VS_OFFCHAIN_MAX_DIF = 250; + + + let ongoingRequest = false; let spotInterruptions = 0; // Global variables to track polling status let pollingInProgress = false; let lastPollingId: string | null = null; let pulledDockerimage = false; - // Cache for network configuration let cachedNetworkConfig: NetworkConfig | null = null; @@ -385,23 +381,36 @@ async function checkAndFetchIfNeeded(client: Client) { switch (on_chain_avalible_random.availibleRandomValues) { case -1: - console.log("Provider is shutting down"); + console.log("Value is -1"); + console.log("Provider has been shut down by USER..."); + console.log("Go to the provider dashboard to turn back on"); //TODO prepare for shutdown //TODO the async causes it to ovewrite itself break; case -2: console.log("Value is -2"); + console.log("Provider has been shut down by PROCESS..."); + console.log("This is due to One of the following: "); + console.log("Failing to provide random fast enough (Provider is not responding to random requests and considered unhealthy NO SLASH)"); + console.log("Failing to provide the proof for an outstanding random request within the time. (Provider was likely turned off mid random request SMALL SLASH)" ); + console.log("Failing to provide the correct proof for your original random (Provider was detected as malicious for tampering with the random LARGE SLASH)"); + console.log("Go to the provider dashboard to turn back on"); break; case -3: console.log("Value is -3"); + console.log("Provider has been shut down by PROCESS..."); + console.log("This was likely done as a test or to get the maintainers attention. Contact team if you see this and are not sure why"); + console.log("Go to the provider dashboard to turn back on"); break; default: console.log("Value is not -1, -2, or -3"); - console.log("Value is "+ on_chain_avalible_random.availibleRandomValues) - if (on_chain_avalible_random.availibleRandomValues != currentCount) { + console.log("Provider is up and working"); + console.log("Onchain Value is "+ on_chain_avalible_random.availibleRandomValues) + console.log("Local Value is "+ currentCount) + if (Math.abs(on_chain_avalible_random.availibleRandomValues - currentCount) > UNCHAIN_VS_OFFCHAIN_MAX_DIF) { console.log(`Updating available random values from ${on_chain_avalible_random.availibleRandomValues} to ${currentCount}`); updateAvailableValuesAsync(currentCount); - } + } } if (ongoingRequest) return; // Prevent redundant operations diff --git a/requester/package.json b/requester/package.json index 57d291a..d6ef358 100644 --- a/requester/package.json +++ b/requester/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "3.7.3", + "ao-process-clients": "5.3.14", "ao-vrf": "file:", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", diff --git a/requester/src/app.ts b/requester/src/app.ts index 673c16c..2e369cb 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -1,12 +1,15 @@ import { + ProviderStakingClient, getRandomClientAutoConfiguration, IRandomClient, RandomClient, RandomClientConfig, StakingClient, + ProviderProfileClient, + RandomClientConfigBuilder, } from "ao-process-clients"; -const RETRY_DELAY_MS = 1000; // 1 second +const RETRY_DELAY_MS = 10000; // 10 seconds const PROVIDER_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes const PROVIDER_REQUEST_TIMEOUT = 60 * 1000; // 1 minute const CHANCE_TO_CALL_RANDOM = 1; @@ -14,19 +17,20 @@ const CHANCE_TO_CALL_RANDOM = 1; let cachedProviders: string[] = []; let lastProviderRefresh = 0; -const RANDOM_CONFIG: RandomClientConfig = { - tokenProcessId: "5ZR9uegKoEhE9fJMbs-MvWLIztMNCVxgpzfeBVE3vqI", - processId: "yKVS1tYE3MajUpZqEIORmW1J8HTke-6o6o6tnlkFOZQ", - wallet: JSON.parse(process.env.REQUEST_WALLET_JSON!), - environment: "mainnet", -}; - +async function getRandomClient(): Promise{ + // let test = await getRandomClientAutoConfiguration() + // test.wallet = JSON.parse(process.env.WALLET_JSON!) + + const RANDOM_CONFIG: RandomClientConfig = await new RandomClientConfigBuilder().withWallet(JSON.parse(process.env.REQUEST_WALLET_JSON!)).build() + const randclient = new RandomClient(RANDOM_CONFIG) + return randclient + } let totalRandomCalled = 0; let totalTimeToFulfill = 0; let fulfilledRequests = 0; const outstandingRequests: Set = new Set(); -async function getRandomProviders(stakeclient: StakingClient): Promise<{ providers: string[], count: number }> { +async function getRandomProviders(randclient: RandomClient): Promise<{ providers: string[], count: number }> { const now = Date.now(); // If we have cached providers and they're not expired, use them @@ -49,9 +53,11 @@ async function getRandomProviders(stakeclient: StakingClient): Promise<{ provide // Create the actual provider fetch promise const fetchPromise = async () => { - const providerInfo = await stakeclient.getAllProvidersInfo(); + const providerInfo = await randclient.getAllProviderActivity(); const eligibleProviders = providerInfo + //@ts-ignore .filter(provider => provider.active === 1) + //@ts-ignore .map(provider => provider.provider_id); if (eligibleProviders.length === 0) { @@ -96,8 +102,8 @@ async function getRandomProviders(stakeclient: StakingClient): Promise<{ provide } async function main() { - const randclient = new RandomClient(RANDOM_CONFIG); - const stakeclient = new StakingClient(RANDOM_CONFIG); + const randclient = await getRandomClient() + //const stakeclient = ProviderStakingClient.autoConfiguration(); while (true) { console.log("Running") @@ -106,7 +112,7 @@ async function main() { if (Math.random() < CHANCE_TO_CALL_RANDOM) { console.log("Initiating random request..."); const callbackId = `callback-${Date.now()}`; - const { providers, count } = await getRandomProviders(stakeclient); + const { providers, count } = await getRandomProviders(randclient); console.log(`Selected ${count} providers:`, providers); await randclient.createRequest(providers, count, callbackId); totalRandomCalled++; From 84471c3d03c7d4e20a4930adb10de27ab6e13856 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 18 Mar 2025 16:41:41 -0400 Subject: [PATCH 33/51] done --- docker-compose/.env.example | 1 - docker-compose/dev-docker-compose.yml | 6 +-- docker-compose/docker-compose.yml | 2 +- docker-compose/mass-dev-docker-compose.yml | 32 +++++++-------- orchestrator/Dockerfile | 2 +- orchestrator/docs/development.md | 2 +- orchestrator/package.json | 2 +- orchestrator/src/app.ts | 40 ++++++++++++++----- .../{clear_outputs.ts => clear_outputs.tzs} | 0 orchestrator/src/{stake.ts => stake.tzs} | 0 orchestrator/tsconfig.json | 12 ++++-- requester/package.json | 2 +- requester/tsconfig.json | 4 +- terraform/ecs.tf | 2 +- 14 files changed, 66 insertions(+), 41 deletions(-) rename orchestrator/src/{clear_outputs.ts => clear_outputs.tzs} (100%) rename orchestrator/src/{stake.ts => stake.tzs} (100%) diff --git a/docker-compose/.env.example b/docker-compose/.env.example index 80d1ef5..62a84ab 100644 --- a/docker-compose/.env.example +++ b/docker-compose/.env.example @@ -13,4 +13,3 @@ WALLET_JSON = '{ "dq": "test", "qi": "test" }' -PROVIDER_ID = "XUo8jZtUDBFLtp5okR12oLrqIZ4ewNlTpqnqmriihJE" diff --git a/docker-compose/dev-docker-compose.yml b/docker-compose/dev-docker-compose.yml index b8a0e10..4833642 100644 --- a/docker-compose/dev-docker-compose.yml +++ b/docker-compose/dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.3.3 + image: randao/orchestrator:v0.3.45 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.3.3 + image: randao/orchestrator:v0.3.45 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.3.3 + image: randao/orchestrator:v0.3.45 depends_on: postgres3: condition: service_healthy diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 3d7b40c..ae00b9e 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.3.2 + image: randao/orchestrator:v0.3.45 depends_on: postgres: condition: service_healthy diff --git a/docker-compose/mass-dev-docker-compose.yml b/docker-compose/mass-dev-docker-compose.yml index f5f27cf..5a689fc 100644 --- a/docker-compose/mass-dev-docker-compose.yml +++ b/docker-compose/mass-dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres3: condition: service_healthy @@ -138,7 +138,7 @@ services: retries: 5 orchestrator4: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres4: condition: service_healthy @@ -177,7 +177,7 @@ services: retries: 5 orchestrator5: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres5: condition: service_healthy @@ -216,7 +216,7 @@ services: retries: 5 orchestrator6: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres6: condition: service_healthy @@ -255,7 +255,7 @@ services: retries: 5 orchestrator7: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres7: condition: service_healthy @@ -294,7 +294,7 @@ services: retries: 5 orchestrator8: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres8: condition: service_healthy @@ -333,7 +333,7 @@ services: retries: 5 orchestrator9: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres9: condition: service_healthy @@ -372,7 +372,7 @@ services: retries: 5 orchestrator10: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres10: condition: service_healthy @@ -411,7 +411,7 @@ services: retries: 5 orchestrator11: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres11: condition: service_healthy @@ -450,7 +450,7 @@ services: retries: 5 orchestrator12: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres12: condition: service_healthy @@ -489,7 +489,7 @@ services: retries: 5 orchestrator13: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres13: condition: service_healthy @@ -528,7 +528,7 @@ services: retries: 5 orchestrator14: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres14: condition: service_healthy @@ -567,7 +567,7 @@ services: retries: 5 orchestrator15: - image: randao/orchestrator:v0.3.30 + image: randao/orchestrator:v0.3.45 depends_on: postgres15: condition: service_healthy @@ -601,7 +601,7 @@ services: - dbeaver-data:/opt/cloudbeaver/workspace requester: - image: randao/requester:v0.3.0 + image: randao/requester:v0.3.1 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/orchestrator/Dockerfile b/orchestrator/Dockerfile index 0ce3931..3bdc53a 100644 --- a/orchestrator/Dockerfile +++ b/orchestrator/Dockerfile @@ -1,5 +1,5 @@ # Use the official lightweight Node.js image -FROM node:20-bullseye-slim +FROM node:22-bullseye-slim # Create a working directory WORKDIR /usr/src/app diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index ccfaed3..83c8940 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.3.3 # You can change this value to any version you want +export VERSION=v0.3.45 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/package.json b/orchestrator/package.json index a1b1c60..e8f08fa 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "5.3.8", + "ao-process-clients": "5.3.21", "ao-vrf": "file:", "arweave": "^1.15.5", "aws-sdk": "^2.1692.0", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 99ed13c..e06bbc4 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -1,7 +1,7 @@ import { Client } from 'pg'; import Docker from 'dockerode'; import AWS from 'aws-sdk'; -import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, RandomClient, RandomClientConfig, RandomClientConfigBuilder} from "ao-process-clients" +import { BaseClientConfig, BaseClientConfigBuilder, GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, RandomClient, RandomClientConfig, RandomClientConfigBuilder} from "ao-process-clients" import { dbConfig } from './db_config.js'; import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; import Arweave from 'arweave'; @@ -26,6 +26,22 @@ async function getRandomClient(): Promise { return randomClientInstance; } +// async function getRandomClient(): Promise { +// if (!randomClientInstance) { +// const RANDOM_CONFIG: BaseClientConfigBuilder = await new BaseClientConfigBuilder() +// .withWallet(JSON.parse(process.env.WALLET_JSON!)) +// .withAOConfig({ +// CU_URL: "https://cu.randao.net", +// MODE: 'legacy' +// }) +// .build(); + +// randomClientInstance = new RandomClient(RANDOM_CONFIG); +// } + +// return randomClientInstance; +// } + // async function getStakingClient(): Promise{ // let test = await getProviderStakingClientAutoConfiguration() // test.wallet = JSON.parse(process.env.WALLET_JSON!) @@ -311,7 +327,8 @@ function addHexPrefix(value: string): string { function updateAvailableValuesAsync(currentCount: number) { return (async () => { try { - (await getRandomClient()).updateProviderAvailableValues(currentCount); + const randomClient = await getRandomClient(); + await randomClient.updateProviderAvailableValues(currentCount); console.log(`Updated provider values to ${currentCount}`); } catch (error) { console.error("Failed to update provider values:", error); @@ -319,18 +336,19 @@ function updateAvailableValuesAsync(currentCount: number) { })(); } + async function shutdown() { - return (async () => { - try { - let message = await (await getRandomClient()).updateProviderAvailableValues(0); - console.log(message) - console.log(`Updated provider values to ${0}`); - } catch (error) { - console.error("Failed to update provider values:", error); - } - })(); + try { + const randomClient = await getRandomClient(); + let message = await randomClient.updateProviderAvailableValues(0); + console.log(message); + console.log(`Updated provider values to 0`); + } catch (error) { + console.error("Failed to update provider values:", error); + } } + async function getMoreRandom(currentCount: number) { const entriesNeeded = MINIMUM_ENTRIES - currentCount; //TODO this math looked wrong in https://discord.com/channels/1209645894039896074/1333434937537204336/1336000074765045853 diff --git a/orchestrator/src/clear_outputs.ts b/orchestrator/src/clear_outputs.tzs similarity index 100% rename from orchestrator/src/clear_outputs.ts rename to orchestrator/src/clear_outputs.tzs diff --git a/orchestrator/src/stake.ts b/orchestrator/src/stake.tzs similarity index 100% rename from orchestrator/src/stake.ts rename to orchestrator/src/stake.tzs diff --git a/orchestrator/tsconfig.json b/orchestrator/tsconfig.json index bf50834..786e06e 100644 --- a/orchestrator/tsconfig.json +++ b/orchestrator/tsconfig.json @@ -8,11 +8,17 @@ "esModuleInterop": true, "moduleResolution": "node", "resolveJsonModule": true, - "declaration": true + "declaration": true, + "skipLibCheck": true, // ✅ Added to skip library type checking + "typeRoots": ["./node_modules/@types"] // ✅ Added to force correct type resolution }, "include": [ - "src/**/*.ts" -, "src/db_config.ts", "src/clear_all_output_requests.ts", "src/reset_db.mjs" ], + "src/**/*.ts", + "src/db_config.ts", + "src/clear_all_output_requests.ts", + "src/reset_db.mjs", + "src/clear_outputs.tzs" + ], "exclude": [ "node_modules" ] diff --git a/requester/package.json b/requester/package.json index d6ef358..59e9853 100644 --- a/requester/package.json +++ b/requester/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "5.3.14", + "ao-process-clients": "5.3.21", "ao-vrf": "file:", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", diff --git a/requester/tsconfig.json b/requester/tsconfig.json index 493e143..50dc9ee 100644 --- a/requester/tsconfig.json +++ b/requester/tsconfig.json @@ -5,7 +5,9 @@ "module": "commonjs", "target": "es6", "strict": true, - "esModuleInterop": true + "esModuleInterop": true, + "skipLibCheck": true, // ✅ Added to skip library type checking + "typeRoots": ["./node_modules/@types"] // ✅ Added to force correct type resolution }, "include": [ "src/**/*.ts" diff --git a/terraform/ecs.tf b/terraform/ecs.tf index 53e78dd..ccd5cee 100644 --- a/terraform/ecs.tf +++ b/terraform/ecs.tf @@ -62,7 +62,7 @@ resource "aws_ecs_task_definition" "orchestrator_service" { container_definitions = jsonencode([ { name = "orchestrator" - image = "randao/orchestrator:v0.3.2" + image = "randao/orchestrator:v0.3.45" environment = [ { name = "ENVIRONMENT" From 062d82d629ea1c7c827f3e4bbc2a9ed4fac285d2 Mon Sep 17 00:00:00 2001 From: K <111819113+KennySwayzee93@users.noreply.github.com> Date: Thu, 20 Mar 2025 21:19:11 -0400 Subject: [PATCH 34/51] time lock puzzles are through database - folder still needs cleanup of old method --- verifiable-delay-function/.pylintrc | 42 ++++++ .../main_time_lock_puzzle.py | 124 ++++++++++++++++++ .../src/converters/__init__.py | 7 + .../src/converters/rsa_converter.py | 25 ++++ .../converters/time_lock_puzzle_converter.py | 24 ++++ .../src/database/DatabaseService.py | 17 +++ .../src/database/entity/RSAEntity.py | 40 ++++++ .../database/entity/TimeLockPuzzleEntity.py | 36 +++++ .../src/database/entity/__init__.py | 7 + verifiable-delay-function/src/mpc/MPC.py | 35 +++++ verifiable-delay-function/src/mpc/__init__.py | 7 + .../src/mpc/abstract/IMPC.py | 95 ++++++++++++++ .../src/mpc/abstract/__init__.py | 5 + verifiable-delay-function/src/mpc/types.py | 11 ++ .../src/primes/Primes.py | 18 +++ .../src/primes/__init__.py | 6 + .../src/primes/abstract/IPrimes.py | 18 +++ .../src/primes/abstract/__init__.py | 5 + .../src/random/Random.py | 13 ++ .../src/random/__init__.py | 6 + .../src/random/abstract/IRandom.py | 18 +++ .../src/random/abstract/__init__.py | 5 + verifiable-delay-function/src/rsa/RSA.py | 52 ++++++++ verifiable-delay-function/src/rsa/__init__.py | 6 + .../src/rsa/abstract/IRSA.py | 46 +++++++ .../src/rsa/abstract/__init__.py | 0 .../EfficientTimeLockPuzzleSolver.py | 45 +++++++ .../SequentialTimeLockPuzzleSolver.py | 33 +++++ .../src/time_lock_puzzle/TimeLockPuzzle.py | 27 ++++ .../time_lock_puzzle/TimeLockPuzzleBuilder.py | 30 +++++ .../time_lock_puzzle/TimeLockPuzzleFactory.py | 78 +++++++++++ .../src/time_lock_puzzle/__init__.py | 25 ++++ .../IEfficientTimeLockPuzzleSolver.py | 36 +++++ .../ISequentialTimeLockPuzzleSolver.py | 19 +++ .../abstract/ITimeLockPuzzle.py | 30 +++++ .../abstract/ITimeLockPuzzleBuilder.py | 49 +++++++ .../abstract/ITimeLockPuzzleFactory.py | 34 +++++ .../src/time_lock_puzzle/abstract/__init__.py | 0 .../src/time_lock_puzzle/constants.py | 5 + .../verifiable_delay_function.py | 36 +++-- .../test_time_lock_puzzle_converter.py | 50 ------- .../entity/test_time_lock_puzzle_entity.py | 58 -------- ...st_time_lock_puzzle_basic_functionality.py | 45 ------- .../test_time_lock_puzzle_sample.py | 120 ----------------- 44 files changed, 1102 insertions(+), 286 deletions(-) create mode 100644 verifiable-delay-function/.pylintrc create mode 100644 verifiable-delay-function/main_time_lock_puzzle.py create mode 100644 verifiable-delay-function/src/converters/rsa_converter.py create mode 100644 verifiable-delay-function/src/converters/time_lock_puzzle_converter.py create mode 100644 verifiable-delay-function/src/database/DatabaseService.py create mode 100644 verifiable-delay-function/src/database/entity/RSAEntity.py create mode 100644 verifiable-delay-function/src/database/entity/TimeLockPuzzleEntity.py create mode 100644 verifiable-delay-function/src/mpc/MPC.py create mode 100644 verifiable-delay-function/src/mpc/__init__.py create mode 100644 verifiable-delay-function/src/mpc/abstract/IMPC.py create mode 100644 verifiable-delay-function/src/mpc/abstract/__init__.py create mode 100644 verifiable-delay-function/src/mpc/types.py create mode 100644 verifiable-delay-function/src/primes/Primes.py create mode 100644 verifiable-delay-function/src/primes/__init__.py create mode 100644 verifiable-delay-function/src/primes/abstract/IPrimes.py create mode 100644 verifiable-delay-function/src/primes/abstract/__init__.py create mode 100644 verifiable-delay-function/src/random/Random.py create mode 100644 verifiable-delay-function/src/random/__init__.py create mode 100644 verifiable-delay-function/src/random/abstract/IRandom.py create mode 100644 verifiable-delay-function/src/random/abstract/__init__.py create mode 100644 verifiable-delay-function/src/rsa/RSA.py create mode 100644 verifiable-delay-function/src/rsa/__init__.py create mode 100644 verifiable-delay-function/src/rsa/abstract/IRSA.py create mode 100644 verifiable-delay-function/src/rsa/abstract/__init__.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/EfficientTimeLockPuzzleSolver.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/SequentialTimeLockPuzzleSolver.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzle.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleBuilder.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleFactory.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/__init__.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/abstract/IEfficientTimeLockPuzzleSolver.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/abstract/ISequentialTimeLockPuzzleSolver.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzle.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleBuilder.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleFactory.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/abstract/__init__.py create mode 100644 verifiable-delay-function/src/time_lock_puzzle/constants.py delete mode 100644 verifiable-delay-function/tests/converters/test_time_lock_puzzle_converter.py delete mode 100644 verifiable-delay-function/tests/database/entity/test_time_lock_puzzle_entity.py delete mode 100644 verifiable-delay-function/tests/time_lock_puzzle/test_time_lock_puzzle_basic_functionality.py delete mode 100644 verifiable-delay-function/tests/time_lock_puzzle/test_time_lock_puzzle_sample.py diff --git a/verifiable-delay-function/.pylintrc b/verifiable-delay-function/.pylintrc new file mode 100644 index 0000000..4b2ad19 --- /dev/null +++ b/verifiable-delay-function/.pylintrc @@ -0,0 +1,42 @@ +[MASTER] +# Add the gmpy2 module to the list of known third party modules +extension-pkg-whitelist=gmpy2 + +# Python code to execute, usually for sys.path manipulation such as pygtk.require() +init-hook='import sys; sys.path.append(".")' + +[MESSAGES CONTROL] +# Disable specific warnings +disable=C0111, # Missing docstring + C0103, # Invalid name + C0303, # Trailing whitespace + E1101, # No member (since gmpy2 uses dynamic members) + R0903, # Too Few public methods + +[TYPECHECK] +# List of module names for which member attributes should not be checked +ignored-modules=gmpy2 + +# List of classes names for which member attributes should not be checked +ignored-classes=gmpy2.mpz,gmpy2.random_state + +[FORMAT] +# Maximum number of characters on a single line +max-line-length=100 + +# Number of spaces of indent required inside a hanging or continued line +indent-after-paren=4 + +[BASIC] +# Regular expression which should only match function or class names +function-rgx=[a-z_][a-z0-9_]{2,50}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +[REPORTS] +# Set the output format. Available formats are text, parseable, colorized +output-format=colorized + +# Include a brief explanation of each error when errors are displayed +msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} diff --git a/verifiable-delay-function/main_time_lock_puzzle.py b/verifiable-delay-function/main_time_lock_puzzle.py new file mode 100644 index 0000000..dc134a7 --- /dev/null +++ b/verifiable-delay-function/main_time_lock_puzzle.py @@ -0,0 +1,124 @@ +"""Main script for generating and persisting time lock puzzles.""" + +import argparse +import time +from typing import List, Tuple + +from src.converters.rsa_converter import RSAConverter +from src.converters.time_lock_puzzle_converter import TimeLockPuzzleConverter +from src.database.DatabaseService import DatabaseService +from src.database.entity.RSAEntity import RSAEntity +from src.database.entity.TimeLockPuzzleEntity import TimeLockPuzzleEntity +from src.mpc import MPC +from src.mpc.types import MPZ +from src.rsa.RSA import RSA +from src.time_lock_puzzle.TimeLockPuzzle import TimeLockPuzzle +from src.time_lock_puzzle.TimeLockPuzzleFactory import TimeLockPuzzleFactory + +# Constants +BIT_SIZE = 2048 # Size for RSA parameters +TIMING_PARAMETER = MPC.mpz(3_000_000) # Number of squarings required + + +class TimeLockPuzzleService: + """Service class for managing time lock puzzle operations.""" + + def __init__(self, bit_size: int, timing_parameter: MPC.mpz): + """ + Initialize the service. + + Args: + bit_size: Size for RSA parameters + timing_parameter: Number of squarings required + """ + self.factory = TimeLockPuzzleFactory(bit_size, timing_parameter) + self.rsa_converter = RSAConverter() + self.puzzle_converter = TimeLockPuzzleConverter() + + def generate_puzzles(self, amount: int) -> List[Tuple[TimeLockPuzzle, RSA, MPZ]]: + """ + Generate multiple time lock puzzles. + + Args: + amount: Number of puzzles to generate + + Returns: + List of (puzzle, rsa) tuples + """ + print(f"Generating {amount} puzzles...") + start_time = time.time() + puzzles = self.factory.create_puzzles(amount) + + total_time = time.time() - start_time + print(f"Puzzle generation took {total_time:.2f} seconds") + return puzzles + + def convert_to_entities( + self, puzzles: List[Tuple[TimeLockPuzzle, RSA, MPZ]] + ) -> List[TimeLockPuzzleEntity | RSAEntity]: + """ + Convert puzzles and RSAs to database entities. + + Args: + puzzles: List of (puzzle, rsa) tuples + + Returns: + List of entities to save + """ + print("\nConverting to entities...") + start_time = time.time() + entities = [] + for puzzle, rsa, _ in puzzles: + rsa_entity = self.rsa_converter.to_entity(rsa) + puzzle_entity = self.puzzle_converter.to_entity(puzzle) + entities.extend([rsa_entity, puzzle_entity]) + total_time = time.time() - start_time + print(f"Entity conversion took {total_time:.2f} seconds") + return entities + + def save_entities(self, entities: List[TimeLockPuzzleEntity | RSAEntity]) -> None: + """ + Save entities to database. + + Args: + entities: List of entities to save + """ + print("\nSaving to database...") + start_time = time.time() + DatabaseService.save_many(entities) + total_time = time.time() - start_time + print(f"Database save took {total_time:.2f} seconds") + + +def parse_args() -> argparse.Namespace: + """Parse command line arguments.""" + parser = argparse.ArgumentParser(description="Generate and save time lock puzzles.") + parser.add_argument( + "count", + type=int, + help="Number of time lock puzzles to generate", + ) + return parser.parse_args() + + +def main() -> None: + """Generate time lock puzzles and save them to the database.""" + args = parse_args() + + # Initialize service + service = TimeLockPuzzleService(BIT_SIZE, TIMING_PARAMETER) + + # Generate puzzles + puzzles = service.generate_puzzles(args.count) + + # Convert to entities + entities = service.convert_to_entities(puzzles) + + # Save to database + service.save_entities(entities) + + print("\nDone!") + + +if __name__ == "__main__": + main() diff --git a/verifiable-delay-function/src/converters/__init__.py b/verifiable-delay-function/src/converters/__init__.py index e69de29..6db91fe 100644 --- a/verifiable-delay-function/src/converters/__init__.py +++ b/verifiable-delay-function/src/converters/__init__.py @@ -0,0 +1,7 @@ +"""Converters for database entities.""" + +from .verifiable_delay_function_converter import * +from .time_lock_puzzle_converter import TimeLockPuzzleConverter +from .rsa_converter import RSAConverter + +__all__ = ["TimeLockPuzzleConverter", "RSAConverter"] diff --git a/verifiable-delay-function/src/converters/rsa_converter.py b/verifiable-delay-function/src/converters/rsa_converter.py new file mode 100644 index 0000000..8c5c1c3 --- /dev/null +++ b/verifiable-delay-function/src/converters/rsa_converter.py @@ -0,0 +1,25 @@ +"""Converter for RSA objects.""" + +from src.rsa.RSA import RSA +from src.database.entity.RSAEntity import RSAEntity + + +class RSAConverter: + """Converter for storing RSA parameters in the database.""" + + @staticmethod + def to_entity(rsa: RSA) -> RSAEntity: + """Convert an RSA instance to an RSAEntity. + + Args: + rsa (RSA): The RSA instance to convert + + Returns: + RSAEntity: The database entity + """ + return RSAEntity( + hex(rsa.get_p()), + hex(rsa.get_q()), + hex(rsa.get_N()), + hex(rsa.get_phi()), + ) diff --git a/verifiable-delay-function/src/converters/time_lock_puzzle_converter.py b/verifiable-delay-function/src/converters/time_lock_puzzle_converter.py new file mode 100644 index 0000000..6abedef --- /dev/null +++ b/verifiable-delay-function/src/converters/time_lock_puzzle_converter.py @@ -0,0 +1,24 @@ +"""Converter for time lock puzzle objects.""" + +from src.time_lock_puzzle import TimeLockPuzzle +from src.database.entity.TimeLockPuzzleEntity import TimeLockPuzzleEntity + + +class TimeLockPuzzleConverter: + """Converter between TimeLockPuzzle and TimeLockPuzzleEntity.""" + + @staticmethod + def to_entity(puzzle: TimeLockPuzzle) -> TimeLockPuzzleEntity: + """Convert a TimeLockPuzzle to a TimeLockPuzzleEntity. + + Args: + puzzle (TimeLockPuzzle): The puzzle to convert + + Returns: + TimeLockPuzzleEntity: The database entity + """ + return TimeLockPuzzleEntity( + x_hex=hex(puzzle.get_x()), + t=str(puzzle.get_t()), + N_hex=hex(puzzle.get_N()), + ) diff --git a/verifiable-delay-function/src/database/DatabaseService.py b/verifiable-delay-function/src/database/DatabaseService.py new file mode 100644 index 0000000..0df181e --- /dev/null +++ b/verifiable-delay-function/src/database/DatabaseService.py @@ -0,0 +1,17 @@ +from typing import List +from .mixins.saveable import Saveable + + +class DatabaseService: + """Service class for database operations.""" + + @staticmethod + def save_many(instances: List[Saveable]) -> None: + """ + Save multiple instances to the database. + + Args: + instances: List of Saveable instances to save + """ + for instance in instances: + instance.save() diff --git a/verifiable-delay-function/src/database/entity/RSAEntity.py b/verifiable-delay-function/src/database/entity/RSAEntity.py new file mode 100644 index 0000000..ffdf98f --- /dev/null +++ b/verifiable-delay-function/src/database/entity/RSAEntity.py @@ -0,0 +1,40 @@ +import uuid +from sqlalchemy import Column, String +from sqlalchemy.ext.declarative import declarative_base + +from src.database.mixins.saveable import Saveable +from src.database.database import get_orm_base + +# Define the Base class for ORM models +Base = get_orm_base() + + +class RSAEntity(Base, Saveable): + """Database entity for storing RSA parameters.""" + + __tablename__ = "rsa_parameters" + + id = Column( + String, primary_key=True, default=lambda: str(uuid.uuid4()) + ) # Unique generated string ID + p = Column(String, nullable=False) # Store hex string of prime p + q = Column(String, nullable=False) # Store hex string of prime q + N = Column(String, nullable=False) # Store hex string of modulus N + phi = Column(String, nullable=False) # Store hex string of Euler's totient + + def __repr__(self): + return f"" + + def __init__(self, p_hex: str, q_hex: str, N_hex: str, phi_hex: str): + """Initialize an RSA entity. + + Args: + p_hex (str): Hex string of prime p + q_hex (str): Hex string of prime q + N_hex (str): Hex string of modulus N + phi_hex (str): Hex string of Euler's totient + """ + self.p = p_hex + self.q = q_hex + self.N = N_hex + self.phi = phi_hex diff --git a/verifiable-delay-function/src/database/entity/TimeLockPuzzleEntity.py b/verifiable-delay-function/src/database/entity/TimeLockPuzzleEntity.py new file mode 100644 index 0000000..bb3b4ca --- /dev/null +++ b/verifiable-delay-function/src/database/entity/TimeLockPuzzleEntity.py @@ -0,0 +1,36 @@ +import uuid +from sqlalchemy import Column, String + +from src.database.mixins.saveable import Saveable +from src.database.database import get_orm_base + +# Define the Base class for ORM models +Base = get_orm_base() + + +class TimeLockPuzzleEntity(Base, Saveable): + """Database entity for storing time lock puzzles.""" + + __tablename__ = "time_lock_puzzles" + + id = Column( + String, primary_key=True, default=lambda: str(uuid.uuid4()) + ) # Unique generated string ID + x = Column(String, nullable=False) # Store hex string of input value x + t = Column(String, nullable=False) # Store base 10 string of time parameter t + N = Column(String, nullable=False) # Store hex string of modulus N + + def __repr__(self): + return f"" + + def __init__(self, x_hex: str, t: str, N_hex: str): + """Initialize a time lock puzzle entity. + + Args: + x_hex (str): Hex string of input value x + t (str): Base 10 string of time parameter t + N_hex (str): Hex string of modulus N + """ + self.x = x_hex + self.t = t + self.N = N_hex diff --git a/verifiable-delay-function/src/database/entity/__init__.py b/verifiable-delay-function/src/database/entity/__init__.py index e69de29..6eb1c9b 100644 --- a/verifiable-delay-function/src/database/entity/__init__.py +++ b/verifiable-delay-function/src/database/entity/__init__.py @@ -0,0 +1,7 @@ +"""Database entity models.""" + +from .verifiable_delay_function_entity import VerifiableDelayFunctionEntity +from .TimeLockPuzzleEntity import TimeLockPuzzleEntity +from .RSAEntity import RSAEntity + +__all__ = ["VerifiableDelayFunctionEntity", "TimeLockPuzzleEntity", "RSAEntity"] diff --git a/verifiable-delay-function/src/mpc/MPC.py b/verifiable-delay-function/src/mpc/MPC.py new file mode 100644 index 0000000..169c273 --- /dev/null +++ b/verifiable-delay-function/src/mpc/MPC.py @@ -0,0 +1,35 @@ +import gmpy2 +from .abstract.IMPC import IMPC +from .types import MPZ, RandomState + + +class MPC(IMPC): + """Implementation of multi-precision computing operations.""" + + @staticmethod + def mpz(value: int) -> MPZ: + return gmpy2.mpz(value) + + @staticmethod + def random_state(seed: int) -> RandomState: + return gmpy2.random_state(seed) + + @staticmethod + def mpz_urandomb(state: RandomState, bit_count: int) -> MPZ: + return gmpy2.mpz_urandomb(state, bit_count) + + @staticmethod + def next_prime(value: MPZ) -> MPZ: + return gmpy2.next_prime(value) + + @staticmethod + def powmod(base: MPZ, exp: MPZ, mod: MPZ) -> MPZ: + return gmpy2.powmod(base, exp, mod) + + @staticmethod + def pow(base: MPZ, exp: MPZ) -> MPZ: + return base**exp + + @staticmethod + def mod(value: MPZ, modulus: MPZ) -> MPZ: + return value % modulus # gmpy2 supports % operator for mpz values diff --git a/verifiable-delay-function/src/mpc/__init__.py b/verifiable-delay-function/src/mpc/__init__.py new file mode 100644 index 0000000..ab216aa --- /dev/null +++ b/verifiable-delay-function/src/mpc/__init__.py @@ -0,0 +1,7 @@ +"""Multi-precision computing module.""" + +from .MPC import MPC +from .abstract.IMPC import IMPC +from .types import MPZ, RandomState, T + +__all__ = ["MPC", "IMPC", "MPZ", "RandomState", "T"] diff --git a/verifiable-delay-function/src/mpc/abstract/IMPC.py b/verifiable-delay-function/src/mpc/abstract/IMPC.py new file mode 100644 index 0000000..4912662 --- /dev/null +++ b/verifiable-delay-function/src/mpc/abstract/IMPC.py @@ -0,0 +1,95 @@ +from abc import ABC, abstractmethod +from ..types import MPZ, RandomState + + +class IMPC(ABC): + """Abstract base class defining the interface for multi-precision computing operations.""" + + @staticmethod + @abstractmethod + def mpz(value: int) -> MPZ: + """Convert a Python integer to an mpz. + + Args: + value (int): Integer value to convert + + Returns: + mpz: Multi-precision integer + """ + + @staticmethod + @abstractmethod + def random_state(seed: int) -> RandomState: + """Create a random state from a seed. + + Args: + seed (int): Seed value for random state + + Returns: + mpz: Random state object + """ + + @staticmethod + @abstractmethod + def mpz_urandomb(state: RandomState, bit_count: int) -> MPZ: + """Generate a random integer with specified number of bits. + + Args: + state (mpz): Random state to use + bit_count (int): Number of bits in result + + Returns: + mpz: Random integer + """ + + @staticmethod + @abstractmethod + def next_prime(value: MPZ) -> MPZ: + """Find the next prime number after the given value. + + Args: + value (mpz): Starting value + + Returns: + mpz: Next prime number + """ + + @staticmethod + @abstractmethod + def powmod(base: MPZ, exp: MPZ, mod: MPZ) -> MPZ: + """Compute (base ** exp) % mod efficiently. + + Args: + base (mpz): Base value + exp (mpz): Exponent value + mod (mpz): Modulus value + + Returns: + mpz: Result of modular exponentiation + """ + + @staticmethod + @abstractmethod + def pow(base: MPZ, exp: MPZ) -> MPZ: + """Compute base ** exp. + + Args: + base (mpz): Base value + exp (mpz): Exponent value + + Returns: + mpz: Result of exponentiation + """ + + @staticmethod + @abstractmethod + def mod(value: MPZ, modulus: MPZ) -> MPZ: + """Compute value % modulus. + + Args: + value (mpz): Value to reduce + modulus (mpz): Modulus to reduce by + + Returns: + mpz: Result of modular reduction + """ diff --git a/verifiable-delay-function/src/mpc/abstract/__init__.py b/verifiable-delay-function/src/mpc/abstract/__init__.py new file mode 100644 index 0000000..557827b --- /dev/null +++ b/verifiable-delay-function/src/mpc/abstract/__init__.py @@ -0,0 +1,5 @@ +"""Abstract interfaces for multi-precision computing operations.""" + +from .IMPC import IMPC + +__all__ = ["IMPC"] diff --git a/verifiable-delay-function/src/mpc/types.py b/verifiable-delay-function/src/mpc/types.py new file mode 100644 index 0000000..b9497fd --- /dev/null +++ b/verifiable-delay-function/src/mpc/types.py @@ -0,0 +1,11 @@ +"""Type definitions for multi-precision computing operations.""" + +from typing import TypeVar, NewType +from gmpy2 import mpz as _mpz, random_state as _random_state + +# Define base types from gmpy2 +MPZ = NewType("MPZ", _mpz) +RandomState = NewType("RandomState", _random_state) + +# Generic type variable for numeric operations +T = TypeVar("T", MPZ, int) diff --git a/verifiable-delay-function/src/primes/Primes.py b/verifiable-delay-function/src/primes/Primes.py new file mode 100644 index 0000000..6615545 --- /dev/null +++ b/verifiable-delay-function/src/primes/Primes.py @@ -0,0 +1,18 @@ +from ..mpc import MPC +from ..mpc.types import MPZ +from ..random import Random +from .abstract.IPrimes import IPrimes + + +class Primes(IPrimes): + """Implementation of prime number generation.""" + + @staticmethod + def get_prime(bit_size: int) -> MPZ: + # Get random state for generating random numbers + rand = Random.get_random(bit_size) + + random_num = MPC.mpz_urandomb(rand, bit_size) + + # Get next prime after the random number + return MPC.next_prime(random_num) diff --git a/verifiable-delay-function/src/primes/__init__.py b/verifiable-delay-function/src/primes/__init__.py new file mode 100644 index 0000000..52cdf79 --- /dev/null +++ b/verifiable-delay-function/src/primes/__init__.py @@ -0,0 +1,6 @@ +"""Prime number generation module.""" + +from .Primes import Primes +from .abstract.IPrimes import IPrimes + +__all__ = ["Primes", "IPrimes"] diff --git a/verifiable-delay-function/src/primes/abstract/IPrimes.py b/verifiable-delay-function/src/primes/abstract/IPrimes.py new file mode 100644 index 0000000..2d49682 --- /dev/null +++ b/verifiable-delay-function/src/primes/abstract/IPrimes.py @@ -0,0 +1,18 @@ +from abc import ABC, abstractmethod +from ...mpc.types import MPZ + + +class IPrimes(ABC): + """Abstract base class defining the interface for prime number generation.""" + + @staticmethod + @abstractmethod + def get_prime(bit_size: int) -> MPZ: + """Get a random prime number. + + Args: + bit_size (int): Number of bits for the prime number. + + Returns: + MPZ: A random prime number + """ diff --git a/verifiable-delay-function/src/primes/abstract/__init__.py b/verifiable-delay-function/src/primes/abstract/__init__.py new file mode 100644 index 0000000..0ee899c --- /dev/null +++ b/verifiable-delay-function/src/primes/abstract/__init__.py @@ -0,0 +1,5 @@ +"""Abstract interfaces for prime number generation.""" + +from .IPrimes import IPrimes + +__all__ = ["IPrimes"] diff --git a/verifiable-delay-function/src/random/Random.py b/verifiable-delay-function/src/random/Random.py new file mode 100644 index 0000000..d5e1458 --- /dev/null +++ b/verifiable-delay-function/src/random/Random.py @@ -0,0 +1,13 @@ +import secrets +from ..mpc import MPC +from ..mpc.types import RandomState +from .abstract.IRandom import IRandom + + +class Random(IRandom): + """Implementation of secure random number generation.""" + + @staticmethod + def get_random(bit_size: int) -> RandomState: + secure_seed = secrets.randbits(bit_size) + return MPC.random_state(secure_seed) diff --git a/verifiable-delay-function/src/random/__init__.py b/verifiable-delay-function/src/random/__init__.py new file mode 100644 index 0000000..3c8b236 --- /dev/null +++ b/verifiable-delay-function/src/random/__init__.py @@ -0,0 +1,6 @@ +"""Random number generation module.""" + +from .Random import Random +from .abstract.IRandom import IRandom + +__all__ = ["Random", "IRandom"] diff --git a/verifiable-delay-function/src/random/abstract/IRandom.py b/verifiable-delay-function/src/random/abstract/IRandom.py new file mode 100644 index 0000000..2e6a0ce --- /dev/null +++ b/verifiable-delay-function/src/random/abstract/IRandom.py @@ -0,0 +1,18 @@ +from abc import ABC, abstractmethod +from ...mpc.types import RandomState + + +class IRandom(ABC): + """Abstract base class defining the interface for random number generation.""" + + @staticmethod + @abstractmethod + def get_random(bit_size: int) -> RandomState: + """Get a random state initialized with a secure seed. + + Args: + bit_size (int): Number of bits for the secure seed. + + Returns: + RandomState: A random state initialized with a secure seed + """ diff --git a/verifiable-delay-function/src/random/abstract/__init__.py b/verifiable-delay-function/src/random/abstract/__init__.py new file mode 100644 index 0000000..b22a9fd --- /dev/null +++ b/verifiable-delay-function/src/random/abstract/__init__.py @@ -0,0 +1,5 @@ +"""Abstract interfaces for random number generation.""" + +from .IRandom import IRandom + +__all__ = ["IRandom"] diff --git a/verifiable-delay-function/src/rsa/RSA.py b/verifiable-delay-function/src/rsa/RSA.py new file mode 100644 index 0000000..45b5bae --- /dev/null +++ b/verifiable-delay-function/src/rsa/RSA.py @@ -0,0 +1,52 @@ +from ..mpc import MPC +from ..mpc.types import MPZ +from .abstract.IRSA import IRSA +from ..primes import Primes + + +class RSA(IRSA): + """Implementation of RSA cryptosystem.""" + + def __init__(self, bit_size: int) -> None: + """Initialize RSA by generating two random prime numbers. + + Args: + bit_size (int): Number of bits for RSA modulus. + Each prime will be bit_size/2 bits. + """ + # Generate two random prime numbers + prime_size = ( + bit_size // 2 - 1 + ) # Each prime is half the size TODO is this needed anymore with gmpc on chain? + self._p = Primes.get_prime(prime_size) + self._q = Primes.get_prime(prime_size) + + # Calculate modulus N and Euler's totient + self._N = self._calculate_N() + self._phi = self._calculate_phi() + + def get_p(self) -> MPZ: + return self._p + + def get_q(self) -> MPZ: + return self._q + + def get_N(self) -> MPZ: + return self._N + + def get_phi(self) -> MPZ: + return self._phi + + def get_eulers_totient(self) -> MPZ: + return self.get_phi() + + # Private methods + # -------------- + + def _calculate_N(self) -> MPZ: + """Calculate the RSA modulus N = p * q.""" + return MPC.mpz(self._p * self._q) + + def _calculate_phi(self) -> MPZ: + """Calculate Euler's totient φ(N) = (p-1)(q-1).""" + return MPC.mpz((self._p - 1) * (self._q - 1)) diff --git a/verifiable-delay-function/src/rsa/__init__.py b/verifiable-delay-function/src/rsa/__init__.py new file mode 100644 index 0000000..4ac513e --- /dev/null +++ b/verifiable-delay-function/src/rsa/__init__.py @@ -0,0 +1,6 @@ +"""RSA cryptosystem module.""" + +from .RSA import RSA +from .abstract.IRSA import IRSA + +__all__ = ["RSA", "IRSA"] diff --git a/verifiable-delay-function/src/rsa/abstract/IRSA.py b/verifiable-delay-function/src/rsa/abstract/IRSA.py new file mode 100644 index 0000000..9897017 --- /dev/null +++ b/verifiable-delay-function/src/rsa/abstract/IRSA.py @@ -0,0 +1,46 @@ +from abc import ABC, abstractmethod +from ...mpc.types import MPZ + + +class IRSA(ABC): + """Abstract base class defining the interface for RSA cryptosystem implementation.""" + + @abstractmethod + def get_p(self) -> MPZ: + """Get the first prime factor p. + + Returns: + MPZ: The prime number p + """ + + @abstractmethod + def get_q(self) -> MPZ: + """Get the second prime factor q. + + Returns: + MPZ: The prime number q + """ + + @abstractmethod + def get_N(self) -> MPZ: + """Get the modulus N = p * q. + + Returns: + MPZ: The modulus N + """ + + @abstractmethod + def get_phi(self) -> MPZ: + """Get Euler's totient φ(N) = (p-1)(q-1). + + Returns: + MPZ: The value of Euler's totient function + """ + + @abstractmethod + def get_eulers_totient(self) -> MPZ: + """Alias for get_phi(). + + Returns: + MPZ: The value of Euler's totient function + """ diff --git a/verifiable-delay-function/src/rsa/abstract/__init__.py b/verifiable-delay-function/src/rsa/abstract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/verifiable-delay-function/src/time_lock_puzzle/EfficientTimeLockPuzzleSolver.py b/verifiable-delay-function/src/time_lock_puzzle/EfficientTimeLockPuzzleSolver.py new file mode 100644 index 0000000..996909f --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/EfficientTimeLockPuzzleSolver.py @@ -0,0 +1,45 @@ +from multiprocessing import Pool +from typing import List, Tuple + +from ..mpc import MPC +from ..mpc.types import MPZ +from ..rsa.RSA import RSA +from .abstract.IEfficientTimeLockPuzzleSolver import IEfficientTimeLockPuzzleSolver +from .abstract.ITimeLockPuzzle import ITimeLockPuzzle +from .constants import TWO + + +class EfficientTimeLockPuzzleSolver(IEfficientTimeLockPuzzleSolver): + """Implementation of efficient time lock puzzle solver using RSA private parameters.""" + + @staticmethod + def solve(rsa: RSA, puzzle: ITimeLockPuzzle) -> MPZ: + # Calculate y = x^(2^t) mod N efficiently using phi + # Calculate 2^t + exp = MPC.pow(TWO, puzzle.get_t()) # 2^t + phi = rsa.get_phi() + d = MPC.mod(exp, phi) # Reduce exponent modulo phi + return MPC.powmod(puzzle.get_x(), d, puzzle.get_N()) + + @staticmethod + def solve_many(puzzles: List[Tuple[RSA, ITimeLockPuzzle]]) -> List[MPZ]: + """ + Solve multiple time lock puzzles in parallel using multiprocessing. + + Args: + puzzles: List of tuples containing (RSA, puzzle) pairs to solve + + Returns: + List of solutions in the same order as input puzzles + """ + with Pool() as pool: + return pool.map(EfficientTimeLockPuzzleSolver._solve_single, puzzles) + + # Private Methods + # -------------- + + @staticmethod + def _solve_single(args: Tuple[RSA, ITimeLockPuzzle]) -> MPZ: + """Helper method to solve a single puzzle for multiprocessing.""" + rsa, puzzle = args + return EfficientTimeLockPuzzleSolver.solve(rsa, puzzle) diff --git a/verifiable-delay-function/src/time_lock_puzzle/SequentialTimeLockPuzzleSolver.py b/verifiable-delay-function/src/time_lock_puzzle/SequentialTimeLockPuzzleSolver.py new file mode 100644 index 0000000..0144200 --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/SequentialTimeLockPuzzleSolver.py @@ -0,0 +1,33 @@ +from ..mpc import MPC +from ..mpc.types import MPZ +from .abstract.ISequentialTimeLockPuzzleSolver import ISequentialTimeLockPuzzleSolver +from .abstract.ITimeLockPuzzle import ITimeLockPuzzle +from .constants import TWO + + +class SequentialTimeLockPuzzleSolver(ISequentialTimeLockPuzzleSolver): + """Implementation of sequential time lock puzzle solver.""" + + @staticmethod + def solve(puzzle: ITimeLockPuzzle) -> MPZ: + """Solve the time lock puzzle sequentially without RSA private parameters. + + This implementation: + 1. Calculates 2^t directly + 2. Then computes x^(2^t) mod N in one step using powmod + + Args: + puzzle (ITimeLockPuzzle): The puzzle to solve + + Returns: + MPZ: The solution y + """ + x = puzzle.get_x() + N = puzzle.get_N() + t = puzzle.get_t() + + # Calculate 2^t first + exp = MPC.pow(TWO, t) + + # Then calculate x^(2^t) mod N in one step + return MPC.powmod(x, exp, N) diff --git a/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzle.py b/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzle.py new file mode 100644 index 0000000..65a0a51 --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzle.py @@ -0,0 +1,27 @@ +from ..mpc.types import MPZ +from .abstract.ITimeLockPuzzle import ITimeLockPuzzle + + +class TimeLockPuzzle(ITimeLockPuzzle): + """Implementation of a time lock puzzle.""" + + def __init__(self, x: MPZ, t: MPZ, N: MPZ) -> None: + """Initialize a time lock puzzle. + + Args: + x (MPZ): The input value + t (MPZ): The time parameter + N (MPZ): The modulus + """ + self._x = x + self._t = t + self._N = N + + def get_x(self) -> MPZ: + return self._x + + def get_t(self) -> MPZ: + return self._t + + def get_N(self) -> MPZ: + return self._N diff --git a/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleBuilder.py b/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleBuilder.py new file mode 100644 index 0000000..be6e9da --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleBuilder.py @@ -0,0 +1,30 @@ +from typing import Self +from ..mpc.types import MPZ +from .TimeLockPuzzle import TimeLockPuzzle +from .abstract.ITimeLockPuzzleBuilder import ITimeLockPuzzleBuilder + + +class TimeLockPuzzleBuilder(ITimeLockPuzzleBuilder): + """Implementation of time lock puzzle builder.""" + + def __init__(self) -> None: + self._x = None + self._t = None + self._N = None + + def set_x(self, x: MPZ) -> Self: + self._x = x + return self + + def set_t(self, t: MPZ) -> Self: + self._t = t + return self + + def set_N(self, N: MPZ) -> Self: + self._N = N + return self + + def build(self) -> TimeLockPuzzle: + if self._x is None or self._t is None or self._N is None: + raise ValueError("All parameters (x, t, N) must be set before building") + return TimeLockPuzzle(self._x, self._t, self._N) diff --git a/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleFactory.py b/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleFactory.py new file mode 100644 index 0000000..19380a2 --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleFactory.py @@ -0,0 +1,78 @@ +from typing import List, Tuple +import multiprocessing + +from src.time_lock_puzzle import TimeLockPuzzleBuilder +from ..mpc import MPC +from ..mpc.types import MPZ +from ..random import Random +from ..rsa.RSA import RSA +from .TimeLockPuzzle import TimeLockPuzzle +from .EfficientTimeLockPuzzleSolver import EfficientTimeLockPuzzleSolver +from .abstract.ITimeLockPuzzleFactory import ITimeLockPuzzleFactory + + +class TimeLockPuzzleFactory(ITimeLockPuzzleFactory): + """Implementation of time lock puzzle factory.""" + + def __init__(self, bit_size: int, timing_parameter: MPZ) -> None: + """Initialize the factory. + + Args: + bit_size (int): Number of bits for RSA parameters + timing_parameter (MPZ): Time parameter t for puzzles + """ + self._bit_size = bit_size + self._t = timing_parameter + + def create_puzzle(self) -> Tuple[TimeLockPuzzle, RSA, MPZ]: + # Create RSA instance + rsa_instance = RSA(self._bit_size) + + # Generate random x + rand = Random.get_random(self._bit_size) + x = MPC.mpz_urandomb(rand, self._bit_size) + + # Create puzzle using builder + puzzle = ( + TimeLockPuzzleBuilder() + .set_x(x) + .set_t(self._t) + .set_N(rsa_instance.get_N()) + .build() + ) + + # Get solution using efficient solver + y = EfficientTimeLockPuzzleSolver.solve(rsa_instance, puzzle) + + return puzzle, rsa_instance, y + + def create_puzzles(self, amount: int) -> List[Tuple[TimeLockPuzzle, RSA, MPZ]]: + # Create parameters for each puzzle + puzzle_params = [(self._bit_size, self._t) for _ in range(amount)] + + # Create puzzles in parallel using process pool + with multiprocessing.Pool() as pool: + puzzles = pool.map( + TimeLockPuzzleFactory._create_puzzle_parallel, puzzle_params + ) + + return puzzles + + # Private Methods + # ------------------------------------------------------------------------------ + + @staticmethod + def _create_puzzle_parallel( + puzzle_params: Tuple[int, MPZ], + ) -> Tuple[TimeLockPuzzle, RSA, MPZ]: + """Helper method to create a single puzzle tuple for multiprocessing. + + Args: + puzzle_params (Tuple[int, MPZ]): Tuple containing (bit_size, timing_parameter) + + Returns: + Tuple[TimeLockPuzzle, RSA, MPZ]: A tuple containing the puzzle, RSA instance, and solution + """ + bit_size, t = puzzle_params + factory = TimeLockPuzzleFactory(bit_size, t) + return factory.create_puzzle() diff --git a/verifiable-delay-function/src/time_lock_puzzle/__init__.py b/verifiable-delay-function/src/time_lock_puzzle/__init__.py new file mode 100644 index 0000000..33481c8 --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/__init__.py @@ -0,0 +1,25 @@ +"""Time lock puzzle module.""" + +from .TimeLockPuzzle import TimeLockPuzzle +from .TimeLockPuzzleBuilder import TimeLockPuzzleBuilder +from .TimeLockPuzzleFactory import TimeLockPuzzleFactory +from .EfficientTimeLockPuzzleSolver import EfficientTimeLockPuzzleSolver +from .SequentialTimeLockPuzzleSolver import SequentialTimeLockPuzzleSolver +from .abstract.ITimeLockPuzzle import ITimeLockPuzzle +from .abstract.ITimeLockPuzzleBuilder import ITimeLockPuzzleBuilder +from .abstract.ITimeLockPuzzleFactory import ITimeLockPuzzleFactory +from .abstract.IEfficientTimeLockPuzzleSolver import IEfficientTimeLockPuzzleSolver +from .abstract.ISequentialTimeLockPuzzleSolver import ISequentialTimeLockPuzzleSolver + +__all__ = [ + "TimeLockPuzzle", + "TimeLockPuzzleBuilder", + "TimeLockPuzzleFactory", + "EfficientTimeLockPuzzleSolver", + "SequentialTimeLockPuzzleSolver", + "ITimeLockPuzzle", + "ITimeLockPuzzleBuilder", + "ITimeLockPuzzleFactory", + "IEfficientTimeLockPuzzleSolver", + "ISequentialTimeLockPuzzleSolver", +] diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/IEfficientTimeLockPuzzleSolver.py b/verifiable-delay-function/src/time_lock_puzzle/abstract/IEfficientTimeLockPuzzleSolver.py new file mode 100644 index 0000000..f4b66df --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/abstract/IEfficientTimeLockPuzzleSolver.py @@ -0,0 +1,36 @@ +from abc import ABC, abstractmethod +from typing import List, Tuple +from ...mpc.types import MPZ +from ...rsa import RSA +from .ITimeLockPuzzle import ITimeLockPuzzle + + +class IEfficientTimeLockPuzzleSolver(ABC): + """Abstract base class defining the interface for an efficient time lock puzzle solver.""" + + @staticmethod + @abstractmethod + def solve(rsa: RSA, puzzle: ITimeLockPuzzle) -> MPZ: + """Solve the time lock puzzle efficiently using RSA private parameters. + + Args: + rsa (RSA): The RSA instance with private parameters + puzzle (ITimeLockPuzzle): The puzzle to solve + + Returns: + MPZ: The solution y + """ + pass + + @staticmethod + @abstractmethod + def solve_many(puzzles: List[Tuple[RSA, ITimeLockPuzzle]]) -> List[MPZ]: + """Solve multiple time lock puzzles in parallel using multiprocessing. + + Args: + puzzles: List of tuples containing (RSA, puzzle) pairs to solve + + Returns: + List[MPZ]: List of solutions in the same order as input puzzles + """ + pass diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/ISequentialTimeLockPuzzleSolver.py b/verifiable-delay-function/src/time_lock_puzzle/abstract/ISequentialTimeLockPuzzleSolver.py new file mode 100644 index 0000000..c8f6889 --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/abstract/ISequentialTimeLockPuzzleSolver.py @@ -0,0 +1,19 @@ +from abc import ABC, abstractmethod +from ...mpc.types import MPZ +from .ITimeLockPuzzle import ITimeLockPuzzle + + +class ISequentialTimeLockPuzzleSolver(ABC): + """Abstract base class defining the interface for a sequential time lock puzzle solver.""" + + @staticmethod + @abstractmethod + def solve(puzzle: ITimeLockPuzzle) -> MPZ: + """Solve the time lock puzzle sequentially without RSA private parameters. + + Args: + puzzle (ITimeLockPuzzle): The puzzle to solve + + Returns: + MPZ: The solution y + """ diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzle.py b/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzle.py new file mode 100644 index 0000000..74b748d --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzle.py @@ -0,0 +1,30 @@ +from abc import ABC, abstractmethod +from ...mpc.types import MPZ + + +class ITimeLockPuzzle(ABC): + """Abstract base class defining the interface for a time lock puzzle implementation.""" + + @abstractmethod + def get_x(self) -> MPZ: + """Get the input value x. + + Returns: + MPZ: The input value x + """ + + @abstractmethod + def get_t(self) -> MPZ: + """Get the time parameter t. + + Returns: + MPZ: The time parameter t + """ + + @abstractmethod + def get_N(self) -> MPZ: + """Get the modulus N. + + Returns: + MPZ: The modulus N + """ diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleBuilder.py b/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleBuilder.py new file mode 100644 index 0000000..aa24405 --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleBuilder.py @@ -0,0 +1,49 @@ +from abc import ABC, abstractmethod +from typing import Self +from ...mpc.types import MPZ +from ..TimeLockPuzzle import TimeLockPuzzle + + +class ITimeLockPuzzleBuilder(ABC): + """Abstract base class defining the interface for a time lock puzzle builder.""" + + @abstractmethod + def set_x(self, x: MPZ) -> Self: + """Set the input value x. + + Args: + x (MPZ): The input value + + Returns: + ITimeLockPuzzleBuilder: The builder instance for chaining + """ + + @abstractmethod + def set_t(self, t: MPZ) -> Self: + """Set the time parameter t. + + Args: + t (MPZ): The time parameter + + Returns: + ITimeLockPuzzleBuilder: The builder instance for chaining + """ + + @abstractmethod + def set_N(self, N: MPZ) -> Self: + """Set the modulus N. + + Args: + N (MPZ): The modulus + + Returns: + ITimeLockPuzzleBuilder: The builder instance for chaining + """ + + @abstractmethod + def build(self) -> TimeLockPuzzle: + """Build the time lock puzzle. + + Returns: + TimeLockPuzzle: The constructed puzzle + """ diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleFactory.py b/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleFactory.py new file mode 100644 index 0000000..26bb910 --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleFactory.py @@ -0,0 +1,34 @@ +from abc import ABC, abstractmethod +from typing import List, Tuple +from ...mpc.types import MPZ +from ...rsa import RSA +from ..TimeLockPuzzle import TimeLockPuzzle + + +class ITimeLockPuzzleFactory(ABC): + """Abstract base class defining the interface for a time lock puzzle factory.""" + + @abstractmethod + def create_puzzle(self) -> Tuple[TimeLockPuzzle, RSA, MPZ]: + """Create a new time lock puzzle with solution. + + Returns: + Tuple[TimeLockPuzzle, RSA, MPZ]: A tuple containing: + - The time lock puzzle + - The RSA instance used to create the puzzle + - The solution y + """ + + @abstractmethod + def create_puzzles(self, amount: int) -> List[Tuple[TimeLockPuzzle, RSA, MPZ]]: + """Create multiple time lock puzzles with solutions in parallel. + + Args: + amount (int): Number of puzzles to create + + Returns: + List[Tuple[TimeLockPuzzle, RSA, MPZ]]: A list of tuples, each containing: + - The time lock puzzle + - The RSA instance used to create the puzzle + - The solution y + """ diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/__init__.py b/verifiable-delay-function/src/time_lock_puzzle/abstract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/verifiable-delay-function/src/time_lock_puzzle/constants.py b/verifiable-delay-function/src/time_lock_puzzle/constants.py new file mode 100644 index 0000000..acfd082 --- /dev/null +++ b/verifiable-delay-function/src/time_lock_puzzle/constants.py @@ -0,0 +1,5 @@ +"""Constants for time lock puzzle module.""" + +from ..mpc import MPC + +TWO = MPC.mpz(2) diff --git a/verifiable-delay-function/src/verifiable_delay_function/verifiable_delay_function.py b/verifiable-delay-function/src/verifiable_delay_function/verifiable_delay_function.py index 3412628..57cd0e9 100644 --- a/verifiable-delay-function/src/verifiable_delay_function/verifiable_delay_function.py +++ b/verifiable-delay-function/src/verifiable_delay_function/verifiable_delay_function.py @@ -1,21 +1,26 @@ -import gmpy2 -from gmpy2 import mpz, powmod, random_state, mpz_urandomb +"""File for Verifiable Delay Functions Class""" + from typing import Tuple, List from multiprocessing import Pool import secrets +import gmpy2 + +from gmpy2 import mpz, powmod, random_state, mpz_urandomb class VerifiableDelayFunction: def __init__(self, bit_size: int = 2048, T: int = 1000, num_segments: int = 10): # Check if T is divisible by num_segments if T % num_segments != 0: - raise ValueError(f"The total number of squarings (T={T}) must be divisible by the number of segments (num_segments={num_segments}).") - + raise ValueError( + f"The total number of squarings (T={T}) must be divisible by the number of segments (num_segments={num_segments})." + ) + self.bit_size = bit_size self.T = T self.num_segments = num_segments self.segment_length = T // num_segments - + # Seed with a cryptographically secure random integer secure_seed = secrets.randbits(256) self.rand = random_state(secure_seed) @@ -30,12 +35,16 @@ def evaluate(self) -> mpz: self.proof = [] # Instead of squaring `segment_length` times, use exponentiation - segment_exp = 2 ** self.segment_length + segment_exp = 2**self.segment_length # Iterate over each segment and apply the batched exponentiation for segment in range(self.num_segments): - result = powmod(result, segment_exp, self.N) # Exponentiate by 2^segment_length in a single step - self.proof.append(mpz(result)) # Store the intermediate result as part of the proof + result = powmod( + result, segment_exp, self.N + ) # Exponentiate by 2^segment_length in a single step + self.proof.append( + mpz(result) + ) # Store the intermediate result as part of the proof self.y = result return result @@ -61,7 +70,7 @@ def verify_segment(args: Tuple[mpz, mpz, int, mpz]) -> bool: for _ in range(segment_length): result = powmod(result, 2, N) return result == end_value - + def parallel_verify(self, y: mpz, proof: List[mpz]) -> bool: """ Performs parallel verification by checking each proof segment concurrently @@ -79,11 +88,11 @@ def parallel_verify(self, y: mpz, proof: List[mpz]) -> bool: (proof[i - 1] if i > 0 else self.x, proof[i], self.segment_length, self.N) for i in range(len(proof)) ] - + # Step 2: Use multiprocessing Pool to verify each segment in parallel with Pool() as pool: results = pool.map(self.verify_segment, tasks) - + # Step 3: Check if all segments verified successfully if not all(results): print("Parallel verification failed.") @@ -91,15 +100,16 @@ def parallel_verify(self, y: mpz, proof: List[mpz]) -> bool: # Final check: Verify that the last computed segment result matches y return proof[-1] == y + ##Private## def _generate_rsa_modulus(self) -> Tuple[mpz, mpz, mpz]: while True: # Generate p and q with slightly fewer bits p = gmpy2.next_prime(mpz_urandomb(self.rand, self.bit_size // 2 - 1)) q = gmpy2.next_prime(mpz_urandomb(self.rand, self.bit_size // 2 - 1)) - + N = p * q - + # Check if N is within the desired bit size if N.bit_length() <= self.bit_size: return N, p, q diff --git a/verifiable-delay-function/tests/converters/test_time_lock_puzzle_converter.py b/verifiable-delay-function/tests/converters/test_time_lock_puzzle_converter.py deleted file mode 100644 index 5fbacbc..0000000 --- a/verifiable-delay-function/tests/converters/test_time_lock_puzzle_converter.py +++ /dev/null @@ -1,50 +0,0 @@ -import pytest -from unittest.mock import patch -from gmpy2 import mpz -from typing import List -from src.verifiable_delay_function.verifiable_delay_function import VerifiableDelayFunction -from src.database.entity.verifiable_delay_function_entity import VerifiableDelayFunctionEntity -from src.converters.verifiable_delay_function_converter import conver_verifiable_delay_function_to_entity # Adjust the import path if needed - -@pytest.fixture -def mocked_verifiable_delay_function(): - """Fixture to create a VerifiableDelayFunction instance with predefined values for testing.""" - with patch.object(VerifiableDelayFunction, '_generate_rsa_modulus') as mock_modulus, \ - patch.object(VerifiableDelayFunction, '_generate_random_challenge') as mock_challenge: - - # Set known values for N, p, q, and x - mock_modulus.return_value = (mpz(21), mpz(7), mpz(3)) # Small modulus for test - mock_challenge.return_value = mpz(5) # Known value for x - - # Instantiate VerifiableDelayFunction with smaller parameters for quicker tests - puzzle = VerifiableDelayFunction(bit_size=16, T=2, num_segments=1) - puzzle.evaluate() # Generate y and proof based on mocked values - return puzzle - -def test_convert_verifiable_delay_function_to_entity(mocked_verifiable_delay_function): - """Test conversion of a VerifiableDelayFunction instance to VerifiableDelayFunctionEntity with hex string storage.""" - # Perform the conversion - entity: VerifiableDelayFunctionEntity = conver_verifiable_delay_function_to_entity(mocked_verifiable_delay_function) - - # Expected values based on the mocked puzzle's modulus, x, y, and proof - expected_modulus = mocked_verifiable_delay_function.N.digits(16) - expected_input = mocked_verifiable_delay_function.x.digits(16) - expected_output = mocked_verifiable_delay_function.y.digits(16) - expected_proof = [p.digits(16) for p in mocked_verifiable_delay_function.proof] - - # Assertions - assert isinstance(entity, VerifiableDelayFunctionEntity) - assert entity.modulus == expected_modulus - assert entity.input == expected_input - assert entity.output == expected_output - assert entity.proof == expected_proof - -def test_convert_verifiable_delay_function_to_entity_missing_proof(): - """Test that conver_verifiable_delay_function_to_entity raises an error if y or proof is missing.""" - # Create a VerifiableDelayFunction instance - vdf_instance = VerifiableDelayFunction(bit_size=16, T=2, num_segments=1) - - # Do not run evaluate() to keep y and proof unset - # Attempt conversion, expecting a ValueError - with pytest.raises(ValueError, match="The VDF instance must be evaluated and proofed before conversion."): - conver_verifiable_delay_function_to_entity(vdf_instance) \ No newline at end of file diff --git a/verifiable-delay-function/tests/database/entity/test_time_lock_puzzle_entity.py b/verifiable-delay-function/tests/database/entity/test_time_lock_puzzle_entity.py deleted file mode 100644 index fb45b34..0000000 --- a/verifiable-delay-function/tests/database/entity/test_time_lock_puzzle_entity.py +++ /dev/null @@ -1,58 +0,0 @@ -import pytest -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from src.database.database import get_orm_base -from src.database.entity.verifiable_delay_function_entity import VerifiableDelayFunctionEntity - -# Setup in-memory SQLite database for testing -@pytest.fixture(scope="module") -def test_database(): - # Create an in-memory SQLite database engine - engine = create_engine("sqlite:///:memory:") - # Bind the base to this engine - Base = get_orm_base() - Base.metadata.create_all(engine) # Create tables - - # Create a sessionmaker bound to this engine - Session = sessionmaker(bind=engine) - session = Session() - - yield session # Provide the session to tests - - # Teardown: close session and drop tables - session.close() - Base.metadata.drop_all(engine) - -def test_verifiable_delay_function_entity_save(test_database): - """Test the saving functionality of VerifiableDelayFunctionEntity with hex strings.""" - # Create a VerifiableDelayFunctioneEntity instance with hex strings - entity = VerifiableDelayFunctionEntity( - modulus_hex='010203', # Example modulus hex string - input_hex='0405', # Example input hex string - output_hex='0607', # Example output hex string - proof=['proof_segment_1', 'proof_segment_2'] - ) - - # Save the entity to the database - test_database.add(entity) - test_database.commit() - - # Verify that the entity was saved and assigned an ID - saved_entity = test_database.query(VerifiableDelayFunctionEntity).filter_by(id=entity.id).first() - assert saved_entity is not None, "Entity was not saved." - assert saved_entity.id == entity.id - assert saved_entity.modulus == '010203' - assert saved_entity.input == '0405' - assert saved_entity.output == '0607' - assert saved_entity.proof == ['proof_segment_1', 'proof_segment_2'] - -def test_verifiable_delay_function_entity_repr(): - """Test that the __repr__ output of VerifiableDelayFunctionEntity is not empty.""" - entity = VerifiableDelayFunctionEntity( - modulus_hex='010203', # Example modulus hex string - input_hex='0405', # Example input hex string - output_hex='0607', # Example output hex string - proof=['proof_segment_1', 'proof_segment_2'] - ) - repr_output = repr(entity) - assert repr_output, "The __repr__ output is empty." \ No newline at end of file diff --git a/verifiable-delay-function/tests/time_lock_puzzle/test_time_lock_puzzle_basic_functionality.py b/verifiable-delay-function/tests/time_lock_puzzle/test_time_lock_puzzle_basic_functionality.py deleted file mode 100644 index 5244c7d..0000000 --- a/verifiable-delay-function/tests/time_lock_puzzle/test_time_lock_puzzle_basic_functionality.py +++ /dev/null @@ -1,45 +0,0 @@ - -import pytest -from gmpy2 import mpz - - -from src.verifiable_delay_function.verifiable_delay_function import VerifiableDelayFunction - - -@pytest.fixture -def verifiable_delay_function_instance(): - """Fixture to create a VerifiableDelayFunction instance.""" - return VerifiableDelayFunction(bit_size=512, T=100, num_segments=5) # Smaller size for quicker testing - -def test_initialization(verifiable_delay_function_instance): - """Test that VerifiableDelayFunction initializes properly.""" - assert verifiable_delay_function_instance.bit_size == 512 - assert verifiable_delay_function_instance.T == 100 - assert verifiable_delay_function_instance.num_segments == 5 - assert verifiable_delay_function_instance.segment_length == 20 # T // num_segments - -def test_evaluate(verifiable_delay_function_instance): - """Test that the evaluate method runs and produces an expected type and proof segments.""" - y = verifiable_delay_function_instance.evaluate() - assert isinstance(y, mpz) - assert len(verifiable_delay_function_instance.proof) == verifiable_delay_function_instance.num_segments - -def test_generate_proof(verifiable_delay_function_instance): - """Test that generate_proof produces the correct output.""" - y, proof = verifiable_delay_function_instance.generate_proof() - assert isinstance(y, mpz) - assert isinstance(proof, list) - assert len(proof) == verifiable_delay_function_instance.num_segments - -def test_parallel_verify(verifiable_delay_function_instance): - """Test that parallel_verify correctly verifies the generated proof.""" - y, proof = verifiable_delay_function_instance.generate_proof() - assert verifiable_delay_function_instance.parallel_verify(y, proof) - -def test_non_divisible_segments_error(): - """Test that VerifiableDelayFunction raises an error when T is not divisible by num_segments.""" - T = 7 - num_segments = 3 - - with pytest.raises(ValueError): - VerifiableDelayFunction(T=T, num_segments=num_segments) \ No newline at end of file diff --git a/verifiable-delay-function/tests/time_lock_puzzle/test_time_lock_puzzle_sample.py b/verifiable-delay-function/tests/time_lock_puzzle/test_time_lock_puzzle_sample.py deleted file mode 100644 index 5117847..0000000 --- a/verifiable-delay-function/tests/time_lock_puzzle/test_time_lock_puzzle_sample.py +++ /dev/null @@ -1,120 +0,0 @@ -import pytest -from unittest.mock import patch -from gmpy2 import mpz -import pytest -from unittest.mock import patch -from gmpy2 import mpz -from src.verifiable_delay_function.verifiable_delay_function import VerifiableDelayFunction - -@pytest.fixture -def verifiable_delay_function_instance(): - """Fixture to create a VerifiableDelayFunction instance with mocked values.""" - with patch.object(VerifiableDelayFunction, '_generate_rsa_modulus') as mock_modulus, \ - patch.object(VerifiableDelayFunction, '_generate_random_challenge') as mock_challenge: - - # Set known values for N, p, q, and x - mock_modulus.return_value = (mpz(21), mpz(7), mpz(3)) # Small modulus for test - mock_challenge.return_value = mpz(5) # Known value for x - - # Instantiate VerifiableDelayFunction with smaller parameters for quicker tests - puzzle = VerifiableDelayFunction(bit_size=16, T=2, num_segments=1) - return puzzle - -def test_evaluate(verifiable_delay_function_instance): - """Test the evaluate method with fixed values for N and x.""" - y = verifiable_delay_function_instance.evaluate() - - # Expected proof for this known N, x, T, and num_segments - # Calculation: x^2^T mod N - # First squaring: 5^2 = 25, then 25 mod 21 = 4 - # Second squaring: 4^2 = 16, then 16 mod 21 = 16 - expected_proof = [mpz(16)] - expected_y = mpz(16) - - assert y == expected_y # Final y should match last proof segment - assert verifiable_delay_function_instance.proof == expected_proof - -def test_generate_proof(verifiable_delay_function_instance): - """Test generate_proof to ensure it matches the expected output.""" - y, proof = verifiable_delay_function_instance.generate_proof() - - # Expected proof for this known N, x, T, and num_segments - expected_proof = [mpz(16)] - expected_y = mpz(16) - - assert y == expected_y - assert proof == expected_proof - -def test_parallel_verify(verifiable_delay_function_instance): - """Test parallel_verify with the mocked values to ensure verification passes.""" - y, proof = verifiable_delay_function_instance.generate_proof() - assert verifiable_delay_function_instance.parallel_verify(y, proof) # Verification should succeed - -@pytest.fixture -def create_verifiable_delay_function_instance(): - """Factory fixture to create VerifiableDelayFunction instances with varied parameters.""" - def _create_instance(T, num_segments): - with patch.object(VerifiableDelayFunction, '_generate_rsa_modulus') as mock_modulus, \ - patch.object(VerifiableDelayFunction, '_generate_random_challenge') as mock_challenge: - - # Set known values for N, p, q, and x - mock_modulus.return_value = (mpz(21), mpz(7), mpz(3)) # Small modulus for test - mock_challenge.return_value = mpz(5) # Known value for x - - # Instantiate VerifiableDelayFunction with specified T and num_segments - puzzle = VerifiableDelayFunction(bit_size=16, T=T, num_segments=num_segments) - return puzzle - return _create_instance - -def test_evaluate_single_segment(create_verifiable_delay_function_instance): - """Test the evaluate method with a single segment (num_segments=1).""" - puzzle = create_verifiable_delay_function_instance(T=4, num_segments=1) - y = puzzle.evaluate() - - # Calculation for T=4, single segment - # First squaring: 5^2 = 25, then 25 mod 21 = 4 - # Second squaring: 4^2 = 16, then 16 mod 21 = 16 - # Third squaring: 16^2 = 256, then 256 mod 21 = 4 - # Fourth squaring: 4^2 = 16, then 16 mod 21 = 16 - expected_proof = [mpz(16)] - expected_y = mpz(16) - - assert y == expected_y - assert puzzle.proof == expected_proof - -def test_evaluate_multiple_segments(create_verifiable_delay_function_instance): - """Test the evaluate method with multiple segments (num_segments=2).""" - puzzle = create_verifiable_delay_function_instance(T=4, num_segments=2) - y = puzzle.evaluate() - - # Expected proof for T=4, num_segments=2, segment_length=2 - expected_proof = [mpz(16), mpz(16)] - expected_y = mpz(16) - - assert y == expected_y - assert puzzle.proof == expected_proof - -def test_generate_proof_varying_segments(create_verifiable_delay_function_instance): - """Test generate_proof with varying segments.""" - # Test with T=6 and num_segments=3 (segment_length=2) - puzzle = create_verifiable_delay_function_instance(T=6, num_segments=3) - y, proof = puzzle.generate_proof() - - # Expected proof segments based on calculations - expected_proof = [mpz(16), mpz(16), mpz(16)] - expected_y = mpz(16) - - assert y == expected_y - assert proof == expected_proof - -def test_parallel_verify_varying_segments(create_verifiable_delay_function_instance): - """Test parallel_verify with varying segments to ensure verification passes.""" - # Test with T=6 and num_segments=3 - puzzle = create_verifiable_delay_function_instance(T=6, num_segments=3) - y, proof = puzzle.generate_proof() - assert puzzle.parallel_verify(y, proof) # Verification should succeed - - # Test with T=4 and num_segments=2 - puzzle = create_verifiable_delay_function_instance(T=4, num_segments=2) - y, proof = puzzle.generate_proof() - assert puzzle.parallel_verify(y, proof) # Verification should succeed From 70a11b0733dd2643c13de485d23c9d650d3194df Mon Sep 17 00:00:00 2001 From: K <111819113+KennySwayzee93@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:09:45 -0400 Subject: [PATCH 35/51] Time lock puzzles and database and linkage all setup --- .../.env.example | 0 .../.gitignore | 0 .../.pylintrc | 0 .../Dockerfile | 70 +++++------ puzzle-generator/README.md | 27 ++++ .../conftest.py | 0 .../docs/developing.md | 6 +- .../main.py | 12 +- .../requirements.txt | 0 .../src/__init__.py | 0 .../src/converters/__init__.py | 1 - .../src/converters/rsa_converter.py | 0 .../converters/time_lock_puzzle_converter.py | 9 +- .../src/database/DatabaseService.py | 0 .../src/database/__init__.py | 0 .../src/database/constants.py | 0 .../src/database/database.py | 0 .../src/database/entity/RSAEntity.py | 11 +- .../database/entity/TimeLockPuzzleEntity.py | 17 ++- .../src/database/entity/__init__.py | 6 + .../src/database/initialize_db.py | 8 +- .../src/database/mixins/__init__.py | 0 .../src/database/mixins/saveable.py | 0 .../src/mpc/MPC.py | 0 .../src/mpc/__init__.py | 0 .../src/mpc/abstract/IMPC.py | 0 .../src/mpc/abstract/__init__.py | 0 .../src/mpc/types.py | 0 .../src/primes/Primes.py | 0 .../src/primes/__init__.py | 0 .../src/primes/abstract/IPrimes.py | 0 .../src/primes/abstract/__init__.py | 0 puzzle-generator/src/protocol_constants.py | 7 ++ .../src/random/Random.py | 0 .../src/random/__init__.py | 0 .../src/random/abstract/IRandom.py | 0 .../src/random/abstract/__init__.py | 0 .../src/rsa/RSA.py | 0 .../src/rsa/__init__.py | 0 .../src/rsa/abstract/IRSA.py | 0 .../src/rsa/abstract/__init__.py | 0 .../EfficientTimeLockPuzzleSolver.py | 0 .../SequentialTimeLockPuzzleSolver.py | 0 .../src/time_lock_puzzle/TimeLockPuzzle.py | 0 .../time_lock_puzzle/TimeLockPuzzleBuilder.py | 0 .../time_lock_puzzle/TimeLockPuzzleFactory.py | 0 .../src/time_lock_puzzle/__init__.py | 0 .../IEfficientTimeLockPuzzleSolver.py | 0 .../ISequentialTimeLockPuzzleSolver.py | 0 .../abstract/ITimeLockPuzzle.py | 0 .../abstract/ITimeLockPuzzleBuilder.py | 0 .../abstract/ITimeLockPuzzleFactory.py | 0 .../src/time_lock_puzzle/abstract/__init__.py | 0 .../src/time_lock_puzzle/constants.py | 0 verifiable-delay-function/README.md | 26 ---- verifiable-delay-function/main.py | 42 ------- .../verifiable_delay_function_converter.py | 34 ----- .../src/database/entity/__init__.py | 7 -- .../verifiable_delay_function_entity.py | 29 ----- .../src/protocol_constants.py | 5 - .../src/verifiable_delay_function/__init__.py | 0 .../verifiable_delay_function.py | 118 ------------------ 62 files changed, 119 insertions(+), 316 deletions(-) rename {verifiable-delay-function => puzzle-generator}/.env.example (100%) rename {verifiable-delay-function => puzzle-generator}/.gitignore (100%) rename {verifiable-delay-function => puzzle-generator}/.pylintrc (100%) rename {verifiable-delay-function => puzzle-generator}/Dockerfile (96%) create mode 100644 puzzle-generator/README.md rename {verifiable-delay-function => puzzle-generator}/conftest.py (100%) rename {verifiable-delay-function => puzzle-generator}/docs/developing.md (83%) rename verifiable-delay-function/main_time_lock_puzzle.py => puzzle-generator/main.py (91%) rename {verifiable-delay-function => puzzle-generator}/requirements.txt (100%) rename {verifiable-delay-function => puzzle-generator}/src/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/converters/__init__.py (79%) rename {verifiable-delay-function => puzzle-generator}/src/converters/rsa_converter.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/converters/time_lock_puzzle_converter.py (64%) rename {verifiable-delay-function => puzzle-generator}/src/database/DatabaseService.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/database/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/database/constants.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/database/database.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/database/entity/RSAEntity.py (77%) rename {verifiable-delay-function => puzzle-generator}/src/database/entity/TimeLockPuzzleEntity.py (60%) create mode 100644 puzzle-generator/src/database/entity/__init__.py rename {verifiable-delay-function => puzzle-generator}/src/database/initialize_db.py (86%) rename {verifiable-delay-function => puzzle-generator}/src/database/mixins/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/database/mixins/saveable.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/mpc/MPC.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/mpc/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/mpc/abstract/IMPC.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/mpc/abstract/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/mpc/types.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/primes/Primes.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/primes/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/primes/abstract/IPrimes.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/primes/abstract/__init__.py (100%) create mode 100644 puzzle-generator/src/protocol_constants.py rename {verifiable-delay-function => puzzle-generator}/src/random/Random.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/random/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/random/abstract/IRandom.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/random/abstract/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/rsa/RSA.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/rsa/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/rsa/abstract/IRSA.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/rsa/abstract/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/EfficientTimeLockPuzzleSolver.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/SequentialTimeLockPuzzleSolver.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/TimeLockPuzzle.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/TimeLockPuzzleBuilder.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/TimeLockPuzzleFactory.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/abstract/IEfficientTimeLockPuzzleSolver.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/abstract/ISequentialTimeLockPuzzleSolver.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/abstract/ITimeLockPuzzle.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/abstract/ITimeLockPuzzleBuilder.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/abstract/ITimeLockPuzzleFactory.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/abstract/__init__.py (100%) rename {verifiable-delay-function => puzzle-generator}/src/time_lock_puzzle/constants.py (100%) delete mode 100644 verifiable-delay-function/README.md delete mode 100644 verifiable-delay-function/main.py delete mode 100644 verifiable-delay-function/src/converters/verifiable_delay_function_converter.py delete mode 100644 verifiable-delay-function/src/database/entity/__init__.py delete mode 100644 verifiable-delay-function/src/database/entity/verifiable_delay_function_entity.py delete mode 100644 verifiable-delay-function/src/protocol_constants.py delete mode 100644 verifiable-delay-function/src/verifiable_delay_function/__init__.py delete mode 100644 verifiable-delay-function/src/verifiable_delay_function/verifiable_delay_function.py diff --git a/verifiable-delay-function/.env.example b/puzzle-generator/.env.example similarity index 100% rename from verifiable-delay-function/.env.example rename to puzzle-generator/.env.example diff --git a/verifiable-delay-function/.gitignore b/puzzle-generator/.gitignore similarity index 100% rename from verifiable-delay-function/.gitignore rename to puzzle-generator/.gitignore diff --git a/verifiable-delay-function/.pylintrc b/puzzle-generator/.pylintrc similarity index 100% rename from verifiable-delay-function/.pylintrc rename to puzzle-generator/.pylintrc diff --git a/verifiable-delay-function/Dockerfile b/puzzle-generator/Dockerfile similarity index 96% rename from verifiable-delay-function/Dockerfile rename to puzzle-generator/Dockerfile index 1064f5f..9a05905 100644 --- a/verifiable-delay-function/Dockerfile +++ b/puzzle-generator/Dockerfile @@ -1,35 +1,35 @@ -# Use an official Python image as a base -FROM python:3.8-slim - -# Install system dependencies needed for gmpy2 and PostgreSQL connection -RUN apt-get update && apt-get install -y \ - libgmp-dev \ - libmpfr-dev \ - libmpc-dev \ - gcc \ - && rm -rf /var/lib/apt/lists/* - -# Set up a working directory -WORKDIR /app - -# Copy only the requirements file to leverage Docker cache -COPY requirements.txt . - -# Install Python dependencies -RUN pip install --no-cache-dir -r requirements.txt - -# Copy the rest of the application code -COPY . . - -# Set environment variables for PostgreSQL credentials (can be overridden at runtime) -ENV DB_NAME=mydatabase \ - DB_USER=myuser \ - DB_PASSWORD=mypassword \ - DB_HOST=localhost \ - DB_PORT=5432 - -# Expose any necessary ports (optional, specify if your app uses specific ports) -# EXPOSE 8000 - -# Command to run the main script -# CMD ["python", "main.py"] +# Use an official Python image as a base +FROM python:3.8-slim + +# Install system dependencies needed for gmpy2 and PostgreSQL connection +RUN apt-get update && apt-get install -y \ + libgmp-dev \ + libmpfr-dev \ + libmpc-dev \ + gcc \ + && rm -rf /var/lib/apt/lists/* + +# Set up a working directory +WORKDIR /app + +# Copy only the requirements file to leverage Docker cache +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the application code +COPY . . + +# Set environment variables for PostgreSQL credentials (can be overridden at runtime) +ENV DB_NAME=mydatabase \ + DB_USER=myuser \ + DB_PASSWORD=mypassword \ + DB_HOST=localhost \ + DB_PORT=5432 + +# Expose any necessary ports (optional, specify if your app uses specific ports) +# EXPOSE 8000 + +# Command to run the main script +# CMD ["python", "main.py"] diff --git a/puzzle-generator/README.md b/puzzle-generator/README.md new file mode 100644 index 0000000..687e29f --- /dev/null +++ b/puzzle-generator/README.md @@ -0,0 +1,27 @@ +# [🔙](../) Time-Lock Puzzles +This repository section contains an implementation of [Time-Lock Puzzles](https://en.wikipedia.org/wiki/Time-lock_puzzle) as outlined in the seminal paper [Time-lock puzzles and timed-release Crypto](https://people.csail.mit.edu/rivest/pubs/RSW96.pdf) by Ronald L. Rivest, Adi Shamir, and David A. Wagner. + +This Time-Lock Puzzle implementation is part of **RandAO's Randomness Provider** project, designed to provide a reliable source of randomness based on cryptographic time delays. RandAO's Randomness Provider leverages Time-Lock Puzzles to ensure that randomness generation requires a precise amount of sequential computation time, establishing trust and security for applications requiring provably delayed randomness. + +## Table of Contents +- [Overview](#overview) +- [Development](#development) +- [License](#license) + +## Overview +The Time-Lock Puzzle implementation in this repository follows the specifications in the [RSW96 paper](https://people.csail.mit.edu/rivest/pubs/RSW96.pdf), providing a cryptographically secure mechanism for creating puzzles that require a predetermined amount of sequential computation to solve. This feature is crucial for applications in time-released cryptography and decentralized randomness protocols, where it is essential to produce randomness that cannot be accessed before a specific time has elapsed. + +Key features of this Time-Lock Puzzle implementation include: + + - Sequential Computation: The puzzle's design requires a specific number of sequential squaring operations modulo a composite number, ensuring that parallel computing offers no advantage in solving the puzzle. + - Precise Time Calibration: The difficulty of each puzzle can be precisely calibrated based on the computing power available to the solver. + - Efficient Creation: Puzzles can be created efficiently by anyone who knows the factorization of the modulus. + - Secure Message Encryption: The puzzle can securely encrypt a message that remains hidden until the sequential computation is completed. + +This approach enables decentralized protocols to produce randomness that is guaranteed to remain secret for a specific time period, making it ideal for use cases such as secure time-released cryptography, fair contract signing, sealed-bid auctions, and other applications requiring temporal security guarantees. + +## Development +For detailed development guidelines, including contributing, testing, and documentation, please refer to the [Development Documentation](./docs/developing.md). + +## License +This project is licensed under the MIT License. See the [LICENSE file](../LICENSE) for details. diff --git a/verifiable-delay-function/conftest.py b/puzzle-generator/conftest.py similarity index 100% rename from verifiable-delay-function/conftest.py rename to puzzle-generator/conftest.py diff --git a/verifiable-delay-function/docs/developing.md b/puzzle-generator/docs/developing.md similarity index 83% rename from verifiable-delay-function/docs/developing.md rename to puzzle-generator/docs/developing.md index 6322f61..6cd1ee3 100644 --- a/verifiable-delay-function/docs/developing.md +++ b/puzzle-generator/docs/developing.md @@ -1,5 +1,5 @@ # Project Setup -This guide will walk you through setting up and running the Verifiable Delay Function (VDF) project in Python. +This guide will walk you through setting up and running the Time lock puzzle project in Python. ## Prerequisites - Python 3.7+: Make sure you have Python installed on your system. @@ -39,8 +39,10 @@ python src/database/initialize_db.py ## Running the Project To generate a VDF proof and verify it, run the main.py script: ```bash -python main.py +python main.py 10 ``` +Required Command line Arguments: + - count: the number of time lock puzzles to generate and store in the database ## Running the Tests To run the unit tests, use the following command: diff --git a/verifiable-delay-function/main_time_lock_puzzle.py b/puzzle-generator/main.py similarity index 91% rename from verifiable-delay-function/main_time_lock_puzzle.py rename to puzzle-generator/main.py index dc134a7..a7d10d7 100644 --- a/verifiable-delay-function/main_time_lock_puzzle.py +++ b/puzzle-generator/main.py @@ -11,14 +11,11 @@ from src.database.entity.TimeLockPuzzleEntity import TimeLockPuzzleEntity from src.mpc import MPC from src.mpc.types import MPZ +from src.protocol_constants import BIT_SIZE, TIMING_PARAMETER from src.rsa.RSA import RSA from src.time_lock_puzzle.TimeLockPuzzle import TimeLockPuzzle from src.time_lock_puzzle.TimeLockPuzzleFactory import TimeLockPuzzleFactory -# Constants -BIT_SIZE = 2048 # Size for RSA parameters -TIMING_PARAMETER = MPC.mpz(3_000_000) # Number of squarings required - class TimeLockPuzzleService: """Service class for managing time lock puzzle operations.""" @@ -68,9 +65,11 @@ def convert_to_entities( print("\nConverting to entities...") start_time = time.time() entities = [] - for puzzle, rsa, _ in puzzles: + for puzzle, rsa, y in puzzles: + # Convert RSA entity first to get its ID (now generated on creation) rsa_entity = self.rsa_converter.to_entity(rsa) - puzzle_entity = self.puzzle_converter.to_entity(puzzle) + # Create puzzle entity with the generated RSA ID + puzzle_entity = self.puzzle_converter.to_entity(puzzle, rsa_entity.id, y) entities.extend([rsa_entity, puzzle_entity]) total_time = time.time() - start_time print(f"Entity conversion took {total_time:.2f} seconds") @@ -85,6 +84,7 @@ def save_entities(self, entities: List[TimeLockPuzzleEntity | RSAEntity]) -> Non """ print("\nSaving to database...") start_time = time.time() + # Now we can save all entities at once since RSA IDs are generated on creation DatabaseService.save_many(entities) total_time = time.time() - start_time print(f"Database save took {total_time:.2f} seconds") diff --git a/verifiable-delay-function/requirements.txt b/puzzle-generator/requirements.txt similarity index 100% rename from verifiable-delay-function/requirements.txt rename to puzzle-generator/requirements.txt diff --git a/verifiable-delay-function/src/__init__.py b/puzzle-generator/src/__init__.py similarity index 100% rename from verifiable-delay-function/src/__init__.py rename to puzzle-generator/src/__init__.py diff --git a/verifiable-delay-function/src/converters/__init__.py b/puzzle-generator/src/converters/__init__.py similarity index 79% rename from verifiable-delay-function/src/converters/__init__.py rename to puzzle-generator/src/converters/__init__.py index 6db91fe..ee1ed54 100644 --- a/verifiable-delay-function/src/converters/__init__.py +++ b/puzzle-generator/src/converters/__init__.py @@ -1,6 +1,5 @@ """Converters for database entities.""" -from .verifiable_delay_function_converter import * from .time_lock_puzzle_converter import TimeLockPuzzleConverter from .rsa_converter import RSAConverter diff --git a/verifiable-delay-function/src/converters/rsa_converter.py b/puzzle-generator/src/converters/rsa_converter.py similarity index 100% rename from verifiable-delay-function/src/converters/rsa_converter.py rename to puzzle-generator/src/converters/rsa_converter.py diff --git a/verifiable-delay-function/src/converters/time_lock_puzzle_converter.py b/puzzle-generator/src/converters/time_lock_puzzle_converter.py similarity index 64% rename from verifiable-delay-function/src/converters/time_lock_puzzle_converter.py rename to puzzle-generator/src/converters/time_lock_puzzle_converter.py index 6abedef..7b789b5 100644 --- a/verifiable-delay-function/src/converters/time_lock_puzzle_converter.py +++ b/puzzle-generator/src/converters/time_lock_puzzle_converter.py @@ -1,24 +1,29 @@ """Converter for time lock puzzle objects.""" -from src.time_lock_puzzle import TimeLockPuzzle +from src.time_lock_puzzle.TimeLockPuzzle import TimeLockPuzzle from src.database.entity.TimeLockPuzzleEntity import TimeLockPuzzleEntity +from src.mpc.types import MPZ class TimeLockPuzzleConverter: """Converter between TimeLockPuzzle and TimeLockPuzzleEntity.""" @staticmethod - def to_entity(puzzle: TimeLockPuzzle) -> TimeLockPuzzleEntity: + def to_entity(puzzle: TimeLockPuzzle, rsa_id: str, y: MPZ) -> TimeLockPuzzleEntity: """Convert a TimeLockPuzzle to a TimeLockPuzzleEntity. Args: puzzle (TimeLockPuzzle): The puzzle to convert + rsa_id (str): ID of the associated RSA entity + y (MPZ): The y value from the puzzle tuple Returns: TimeLockPuzzleEntity: The database entity """ return TimeLockPuzzleEntity( x_hex=hex(puzzle.get_x()), + y_hex=hex(y), t=str(puzzle.get_t()), N_hex=hex(puzzle.get_N()), + rsa_id=rsa_id, ) diff --git a/verifiable-delay-function/src/database/DatabaseService.py b/puzzle-generator/src/database/DatabaseService.py similarity index 100% rename from verifiable-delay-function/src/database/DatabaseService.py rename to puzzle-generator/src/database/DatabaseService.py diff --git a/verifiable-delay-function/src/database/__init__.py b/puzzle-generator/src/database/__init__.py similarity index 100% rename from verifiable-delay-function/src/database/__init__.py rename to puzzle-generator/src/database/__init__.py diff --git a/verifiable-delay-function/src/database/constants.py b/puzzle-generator/src/database/constants.py similarity index 100% rename from verifiable-delay-function/src/database/constants.py rename to puzzle-generator/src/database/constants.py diff --git a/verifiable-delay-function/src/database/database.py b/puzzle-generator/src/database/database.py similarity index 100% rename from verifiable-delay-function/src/database/database.py rename to puzzle-generator/src/database/database.py diff --git a/verifiable-delay-function/src/database/entity/RSAEntity.py b/puzzle-generator/src/database/entity/RSAEntity.py similarity index 77% rename from verifiable-delay-function/src/database/entity/RSAEntity.py rename to puzzle-generator/src/database/entity/RSAEntity.py index ffdf98f..b2e8b79 100644 --- a/verifiable-delay-function/src/database/entity/RSAEntity.py +++ b/puzzle-generator/src/database/entity/RSAEntity.py @@ -1,6 +1,7 @@ import uuid from sqlalchemy import Column, String from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship from src.database.mixins.saveable import Saveable from src.database.database import get_orm_base @@ -12,15 +13,16 @@ class RSAEntity(Base, Saveable): """Database entity for storing RSA parameters.""" - __tablename__ = "rsa_parameters" + __tablename__ = "rsa_keys" - id = Column( - String, primary_key=True, default=lambda: str(uuid.uuid4()) - ) # Unique generated string ID + id = Column(String, primary_key=True) # Unique generated string ID p = Column(String, nullable=False) # Store hex string of prime p q = Column(String, nullable=False) # Store hex string of prime q N = Column(String, nullable=False) # Store hex string of modulus N phi = Column(String, nullable=False) # Store hex string of Euler's totient + puzzle = relationship( + "TimeLockPuzzleEntity", back_populates="rsa", uselist=False + ) # One-to-one back reference to puzzle def __repr__(self): return f"" @@ -34,6 +36,7 @@ def __init__(self, p_hex: str, q_hex: str, N_hex: str, phi_hex: str): N_hex (str): Hex string of modulus N phi_hex (str): Hex string of Euler's totient """ + self.id = str(uuid.uuid4()) # Generate ID on creation self.p = p_hex self.q = q_hex self.N = N_hex diff --git a/verifiable-delay-function/src/database/entity/TimeLockPuzzleEntity.py b/puzzle-generator/src/database/entity/TimeLockPuzzleEntity.py similarity index 60% rename from verifiable-delay-function/src/database/entity/TimeLockPuzzleEntity.py rename to puzzle-generator/src/database/entity/TimeLockPuzzleEntity.py index bb3b4ca..2abb780 100644 --- a/verifiable-delay-function/src/database/entity/TimeLockPuzzleEntity.py +++ b/puzzle-generator/src/database/entity/TimeLockPuzzleEntity.py @@ -1,5 +1,6 @@ import uuid -from sqlalchemy import Column, String +from sqlalchemy import Column, String, ForeignKey +from sqlalchemy.orm import relationship from src.database.mixins.saveable import Saveable from src.database.database import get_orm_base @@ -17,13 +18,23 @@ class TimeLockPuzzleEntity(Base, Saveable): String, primary_key=True, default=lambda: str(uuid.uuid4()) ) # Unique generated string ID x = Column(String, nullable=False) # Store hex string of input value x + y = Column(String, nullable=False) # Store hex string of y value t = Column(String, nullable=False) # Store base 10 string of time parameter t N = Column(String, nullable=False) # Store hex string of modulus N + request_id = Column( + String, nullable=True + ) # Optional associated randomness request id (will be filled within the provider node runtime) + rsa_id = Column( + String, ForeignKey("rsa_keys.id"), nullable=False, unique=True + ) # One-to-one reference to RSA key + rsa = relationship( + "RSAEntity", back_populates="puzzle" + ) # One-to-one relationship to RSA entity def __repr__(self): return f"" - def __init__(self, x_hex: str, t: str, N_hex: str): + def __init__(self, x_hex: str, y_hex: str, t: str, N_hex: str, rsa_id: str): """Initialize a time lock puzzle entity. Args: @@ -32,5 +43,7 @@ def __init__(self, x_hex: str, t: str, N_hex: str): N_hex (str): Hex string of modulus N """ self.x = x_hex + self.y = y_hex self.t = t self.N = N_hex + self.rsa_id = rsa_id diff --git a/puzzle-generator/src/database/entity/__init__.py b/puzzle-generator/src/database/entity/__init__.py new file mode 100644 index 0000000..4f7ba70 --- /dev/null +++ b/puzzle-generator/src/database/entity/__init__.py @@ -0,0 +1,6 @@ +"""Database entity models.""" + +from .TimeLockPuzzleEntity import TimeLockPuzzleEntity +from .RSAEntity import RSAEntity + +__all__ = ["TimeLockPuzzleEntity", "RSAEntity"] diff --git a/verifiable-delay-function/src/database/initialize_db.py b/puzzle-generator/src/database/initialize_db.py similarity index 86% rename from verifiable-delay-function/src/database/initialize_db.py rename to puzzle-generator/src/database/initialize_db.py index b25acb3..fb236c4 100644 --- a/verifiable-delay-function/src/database/initialize_db.py +++ b/puzzle-generator/src/database/initialize_db.py @@ -5,10 +5,11 @@ from sqlalchemy.exc import OperationalError # Dynamically add the `src` directory to `sys.path` -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) +from src.database.entity import * from src.database.database import get_engine, Base -from src.database.entity.verifiable_delay_function_entity import VerifiableDelayFunctionEntity + def initialize_database(): """ @@ -24,6 +25,7 @@ def initialize_database(): finally: engine.dispose() # Close the engine when done + if __name__ == "__main__": - + initialize_database() diff --git a/verifiable-delay-function/src/database/mixins/__init__.py b/puzzle-generator/src/database/mixins/__init__.py similarity index 100% rename from verifiable-delay-function/src/database/mixins/__init__.py rename to puzzle-generator/src/database/mixins/__init__.py diff --git a/verifiable-delay-function/src/database/mixins/saveable.py b/puzzle-generator/src/database/mixins/saveable.py similarity index 100% rename from verifiable-delay-function/src/database/mixins/saveable.py rename to puzzle-generator/src/database/mixins/saveable.py diff --git a/verifiable-delay-function/src/mpc/MPC.py b/puzzle-generator/src/mpc/MPC.py similarity index 100% rename from verifiable-delay-function/src/mpc/MPC.py rename to puzzle-generator/src/mpc/MPC.py diff --git a/verifiable-delay-function/src/mpc/__init__.py b/puzzle-generator/src/mpc/__init__.py similarity index 100% rename from verifiable-delay-function/src/mpc/__init__.py rename to puzzle-generator/src/mpc/__init__.py diff --git a/verifiable-delay-function/src/mpc/abstract/IMPC.py b/puzzle-generator/src/mpc/abstract/IMPC.py similarity index 100% rename from verifiable-delay-function/src/mpc/abstract/IMPC.py rename to puzzle-generator/src/mpc/abstract/IMPC.py diff --git a/verifiable-delay-function/src/mpc/abstract/__init__.py b/puzzle-generator/src/mpc/abstract/__init__.py similarity index 100% rename from verifiable-delay-function/src/mpc/abstract/__init__.py rename to puzzle-generator/src/mpc/abstract/__init__.py diff --git a/verifiable-delay-function/src/mpc/types.py b/puzzle-generator/src/mpc/types.py similarity index 100% rename from verifiable-delay-function/src/mpc/types.py rename to puzzle-generator/src/mpc/types.py diff --git a/verifiable-delay-function/src/primes/Primes.py b/puzzle-generator/src/primes/Primes.py similarity index 100% rename from verifiable-delay-function/src/primes/Primes.py rename to puzzle-generator/src/primes/Primes.py diff --git a/verifiable-delay-function/src/primes/__init__.py b/puzzle-generator/src/primes/__init__.py similarity index 100% rename from verifiable-delay-function/src/primes/__init__.py rename to puzzle-generator/src/primes/__init__.py diff --git a/verifiable-delay-function/src/primes/abstract/IPrimes.py b/puzzle-generator/src/primes/abstract/IPrimes.py similarity index 100% rename from verifiable-delay-function/src/primes/abstract/IPrimes.py rename to puzzle-generator/src/primes/abstract/IPrimes.py diff --git a/verifiable-delay-function/src/primes/abstract/__init__.py b/puzzle-generator/src/primes/abstract/__init__.py similarity index 100% rename from verifiable-delay-function/src/primes/abstract/__init__.py rename to puzzle-generator/src/primes/abstract/__init__.py diff --git a/puzzle-generator/src/protocol_constants.py b/puzzle-generator/src/protocol_constants.py new file mode 100644 index 0000000..15e0067 --- /dev/null +++ b/puzzle-generator/src/protocol_constants.py @@ -0,0 +1,7 @@ +# protocol_constants.py + +from src.mpc import MPC + + +BIT_SIZE = 2048 # RSA modulus bit size +TIMING_PARAMETER = MPC.mpz(3_000_000) # T - Total squarings for delay diff --git a/verifiable-delay-function/src/random/Random.py b/puzzle-generator/src/random/Random.py similarity index 100% rename from verifiable-delay-function/src/random/Random.py rename to puzzle-generator/src/random/Random.py diff --git a/verifiable-delay-function/src/random/__init__.py b/puzzle-generator/src/random/__init__.py similarity index 100% rename from verifiable-delay-function/src/random/__init__.py rename to puzzle-generator/src/random/__init__.py diff --git a/verifiable-delay-function/src/random/abstract/IRandom.py b/puzzle-generator/src/random/abstract/IRandom.py similarity index 100% rename from verifiable-delay-function/src/random/abstract/IRandom.py rename to puzzle-generator/src/random/abstract/IRandom.py diff --git a/verifiable-delay-function/src/random/abstract/__init__.py b/puzzle-generator/src/random/abstract/__init__.py similarity index 100% rename from verifiable-delay-function/src/random/abstract/__init__.py rename to puzzle-generator/src/random/abstract/__init__.py diff --git a/verifiable-delay-function/src/rsa/RSA.py b/puzzle-generator/src/rsa/RSA.py similarity index 100% rename from verifiable-delay-function/src/rsa/RSA.py rename to puzzle-generator/src/rsa/RSA.py diff --git a/verifiable-delay-function/src/rsa/__init__.py b/puzzle-generator/src/rsa/__init__.py similarity index 100% rename from verifiable-delay-function/src/rsa/__init__.py rename to puzzle-generator/src/rsa/__init__.py diff --git a/verifiable-delay-function/src/rsa/abstract/IRSA.py b/puzzle-generator/src/rsa/abstract/IRSA.py similarity index 100% rename from verifiable-delay-function/src/rsa/abstract/IRSA.py rename to puzzle-generator/src/rsa/abstract/IRSA.py diff --git a/verifiable-delay-function/src/rsa/abstract/__init__.py b/puzzle-generator/src/rsa/abstract/__init__.py similarity index 100% rename from verifiable-delay-function/src/rsa/abstract/__init__.py rename to puzzle-generator/src/rsa/abstract/__init__.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/EfficientTimeLockPuzzleSolver.py b/puzzle-generator/src/time_lock_puzzle/EfficientTimeLockPuzzleSolver.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/EfficientTimeLockPuzzleSolver.py rename to puzzle-generator/src/time_lock_puzzle/EfficientTimeLockPuzzleSolver.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/SequentialTimeLockPuzzleSolver.py b/puzzle-generator/src/time_lock_puzzle/SequentialTimeLockPuzzleSolver.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/SequentialTimeLockPuzzleSolver.py rename to puzzle-generator/src/time_lock_puzzle/SequentialTimeLockPuzzleSolver.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzle.py b/puzzle-generator/src/time_lock_puzzle/TimeLockPuzzle.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzle.py rename to puzzle-generator/src/time_lock_puzzle/TimeLockPuzzle.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleBuilder.py b/puzzle-generator/src/time_lock_puzzle/TimeLockPuzzleBuilder.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleBuilder.py rename to puzzle-generator/src/time_lock_puzzle/TimeLockPuzzleBuilder.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleFactory.py b/puzzle-generator/src/time_lock_puzzle/TimeLockPuzzleFactory.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/TimeLockPuzzleFactory.py rename to puzzle-generator/src/time_lock_puzzle/TimeLockPuzzleFactory.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/__init__.py b/puzzle-generator/src/time_lock_puzzle/__init__.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/__init__.py rename to puzzle-generator/src/time_lock_puzzle/__init__.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/IEfficientTimeLockPuzzleSolver.py b/puzzle-generator/src/time_lock_puzzle/abstract/IEfficientTimeLockPuzzleSolver.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/abstract/IEfficientTimeLockPuzzleSolver.py rename to puzzle-generator/src/time_lock_puzzle/abstract/IEfficientTimeLockPuzzleSolver.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/ISequentialTimeLockPuzzleSolver.py b/puzzle-generator/src/time_lock_puzzle/abstract/ISequentialTimeLockPuzzleSolver.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/abstract/ISequentialTimeLockPuzzleSolver.py rename to puzzle-generator/src/time_lock_puzzle/abstract/ISequentialTimeLockPuzzleSolver.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzle.py b/puzzle-generator/src/time_lock_puzzle/abstract/ITimeLockPuzzle.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzle.py rename to puzzle-generator/src/time_lock_puzzle/abstract/ITimeLockPuzzle.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleBuilder.py b/puzzle-generator/src/time_lock_puzzle/abstract/ITimeLockPuzzleBuilder.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleBuilder.py rename to puzzle-generator/src/time_lock_puzzle/abstract/ITimeLockPuzzleBuilder.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleFactory.py b/puzzle-generator/src/time_lock_puzzle/abstract/ITimeLockPuzzleFactory.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/abstract/ITimeLockPuzzleFactory.py rename to puzzle-generator/src/time_lock_puzzle/abstract/ITimeLockPuzzleFactory.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/abstract/__init__.py b/puzzle-generator/src/time_lock_puzzle/abstract/__init__.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/abstract/__init__.py rename to puzzle-generator/src/time_lock_puzzle/abstract/__init__.py diff --git a/verifiable-delay-function/src/time_lock_puzzle/constants.py b/puzzle-generator/src/time_lock_puzzle/constants.py similarity index 100% rename from verifiable-delay-function/src/time_lock_puzzle/constants.py rename to puzzle-generator/src/time_lock_puzzle/constants.py diff --git a/verifiable-delay-function/README.md b/verifiable-delay-function/README.md deleted file mode 100644 index 59cd891..0000000 --- a/verifiable-delay-function/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# [🔙](../) Verifiable Delay Function (VDF) -This repository section contains an implementation of the [Verifiable Delay Function](https://doi.org/10.4230/LIPIcs.ITCS.2019.60) as outlined by Krzysztof Pietrzak in the paper *Verifiable Delay Functions* (ITCS 2019). - -This VDF implementation is part of **RandAO's Randomness Provider** project, designed to provide a reliable source of randomness based on cryptographic delay. RandAO's Randomness Provider leverages VDFs to ensure that randomness generation is sequential, non-parallelizable, and verifiable, establishing trust and security for applications requiring provably delayed randomness. - -## Table of Contents -- [Overview](#overview) -- [Development](#development) -- [License](#license) - -## Overview -The Verifiable Delay Function (VDF) implemented in this repository follows the specifications in [Pietrzak’s paper](https://doi.org/10.4230/LIPIcs.ITCS.2019.60), providing a cryptographically secure delay mechanism that requires significant serial compute time for generation, yet allows for efficient, parallelized verification. This feature is crucial for applications in decentralized randomness protocols, where it is essential to produce randomness that is both unbiased and verifiable by third parties. - -Key features of this VDF implementation include: - - - Serial Computation for Generation: The VDF’s core design requires sequential calculations to produce the delayed output, ensuring that no shortcut can bypass the intended delay. - - Parallelized Verification: The delayed output is verifiable in a parallelized manner, allowing for efficient proof checks even in distributed environments. - - Secure Random State Initialization: Each VDF instance uses secure, unique seeding for generating the modulus and initial challenge, ensuring cryptographic security across executions. - -This approach enables decentralized protocols to produce and verify randomness that is resistant to tampering or premature access, making it ideal for use cases such as secure lotteries, blockchain protocols, and other decentralized applications requiring provable delay-based randomness. - -## Development -For detailed development guidelines, including contributing, testing, and documentation, please refer to the [Development Documentation](./docs/developing.md). - -## License -This project is licensed under the MIT License. See the [LICENSE file](../LICENSE) for details. \ No newline at end of file diff --git a/verifiable-delay-function/main.py b/verifiable-delay-function/main.py deleted file mode 100644 index f7a4762..0000000 --- a/verifiable-delay-function/main.py +++ /dev/null @@ -1,42 +0,0 @@ -import time -from src.verifiable_delay_function.verifiable_delay_function import VerifiableDelayFunction -from src.database.entity.verifiable_delay_function_entity import VerifiableDelayFunctionEntity -from src.converters.verifiable_delay_function_converter import conver_verifiable_delay_function_to_entity -from src.protocol_constants import BIT_SIZE, TOTAL_SQUARINGS, NUM_SEGMENTS - -def main(): - """ - Initializes a VDF with protocol constants. Generates a proof by performing sequential squarings in - the RSA group, then verifies the proof with parallel verification. Finally, converts the VDF to a - database entity and saves it. - """ - # Initialize VerifiableDelayFunction with protocol constants - vdf = VerifiableDelayFunction(bit_size=BIT_SIZE, T=TOTAL_SQUARINGS, num_segments=NUM_SEGMENTS) - print("Generated RSA modulus N:", vdf.N) - - # Time the proof generation - start_time = time.time() - y, proof = vdf.generate_proof() - generation_time = time.time() - start_time - print("VDF output (y):", y) - print(f"Proof generation time: {generation_time:.4f} seconds") - - # # Time the parallel verification - # start_time = time.time() - # is_valid_parallel = vdf.parallel_verify(y, proof) - # parallel_verification_time = time.time() - start_time - # print("Parallel verification:", "Valid" if is_valid_parallel else "Invalid") - # print(f"Parallel verification time: {parallel_verification_time:.4f} seconds") - - # if not is_valid_parallel: - # print("Verification failed. Aborting save.") - # return - - # Convert the puzzle instance to a VerifiableDelayFunctionEntity for database storage - entity: VerifiableDelayFunctionEntity = conver_verifiable_delay_function_to_entity(vdf) - - # Save the entity to the database - entity.save() - -if __name__ == "__main__": - main() diff --git a/verifiable-delay-function/src/converters/verifiable_delay_function_converter.py b/verifiable-delay-function/src/converters/verifiable_delay_function_converter.py deleted file mode 100644 index 399eba6..0000000 --- a/verifiable-delay-function/src/converters/verifiable_delay_function_converter.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import List -from gmpy2 import mpz -from src.database.entity.verifiable_delay_function_entity import VerifiableDelayFunctionEntity -from src.verifiable_delay_function.verifiable_delay_function import VerifiableDelayFunction - -def conver_verifiable_delay_function_to_entity(verifiable_delay_function: VerifiableDelayFunction) -> VerifiableDelayFunctionEntity: - """ - Converts a completed VerifiableDelayFunction instance into a VerifiableDelayFunctionEntity instance for database storage. - - Args: - vdf_instance (VerifiableDelayFunction): The completed VDF instance to convert. - - Returns: - VerifiableDelayFunctionEntity: A new VerifiableDelayFunctionEntity instance populated with data from the VDF. - """ - # Ensure that `y` and `proof` are available - if not hasattr(verifiable_delay_function, 'y') or not verifiable_delay_function.proof: - raise ValueError("The VDF instance must be evaluated and proofed before conversion.") - - # Convert the modulus, input, and output to hex strings - modulus_hex: str = verifiable_delay_function.N.digits(16) - input_hex: str = verifiable_delay_function.x.digits(16) - output_hex: str = verifiable_delay_function.y.digits(16) - - # Convert proof list to JSON-serializable format by encoding each segment as hex - proof_json: List[str] = [checkpoint.digits(16) for checkpoint in verifiable_delay_function.proof] - - # Create and return a new VerifiableDelayFunctionEntity instance - return VerifiableDelayFunctionEntity( - modulus_hex=modulus_hex, - input_hex=input_hex, - output_hex=output_hex, - proof=proof_json - ) diff --git a/verifiable-delay-function/src/database/entity/__init__.py b/verifiable-delay-function/src/database/entity/__init__.py deleted file mode 100644 index 6eb1c9b..0000000 --- a/verifiable-delay-function/src/database/entity/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Database entity models.""" - -from .verifiable_delay_function_entity import VerifiableDelayFunctionEntity -from .TimeLockPuzzleEntity import TimeLockPuzzleEntity -from .RSAEntity import RSAEntity - -__all__ = ["VerifiableDelayFunctionEntity", "TimeLockPuzzleEntity", "RSAEntity"] diff --git a/verifiable-delay-function/src/database/entity/verifiable_delay_function_entity.py b/verifiable-delay-function/src/database/entity/verifiable_delay_function_entity.py deleted file mode 100644 index bedda4d..0000000 --- a/verifiable-delay-function/src/database/entity/verifiable_delay_function_entity.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import List -import uuid -from sqlalchemy import Column, String, LargeBinary, JSON, Integer -from sqlalchemy.ext.declarative import declarative_base - -from src.database.mixins.saveable import Saveable -from src.database.database import get_orm_base - -# Define the Base class for ORM models -Base = get_orm_base() - -class VerifiableDelayFunctionEntity(Base, Saveable): - __tablename__ = 'verifiable_delay_functions' - - id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4())) # Unique generated string ID - request_id = Column(String, nullable=True) # Nullable request_id to be filled later - modulus = Column(String, nullable=False) # Store hex string of modulus - input = Column(String, nullable=False) # Store hex string of input - output = Column(String, nullable=False) # Store hex string of output - proof = Column(JSON, nullable=False) # Proof as a JSON list of hex strings - - def __repr__(self): - return (f" mpz: - """Optimized evaluation using batched exponentiation to reduce calls to powmod.""" - result = mpz(self.x) - self.proof = [] - - # Instead of squaring `segment_length` times, use exponentiation - segment_exp = 2**self.segment_length - - # Iterate over each segment and apply the batched exponentiation - for segment in range(self.num_segments): - result = powmod( - result, segment_exp, self.N - ) # Exponentiate by 2^segment_length in a single step - self.proof.append( - mpz(result) - ) # Store the intermediate result as part of the proof - - self.y = result - return result - - def generate_proof(self) -> Tuple[mpz, List[mpz]]: - y = self.evaluate() - return y, self.proof - - @staticmethod - def verify_segment(args: Tuple[mpz, mpz, int, mpz]) -> bool: - """ - Verify a segment by performing segment_length squarings. - - Args: - args (Tuple): A tuple containing the start_value, expected end_value, - segment_length, and modulus N. - - Returns: - bool: True if the computed end_value matches the expected end_value, False otherwise. - """ - start_value, end_value, segment_length, N = args - result = mpz(start_value) - for _ in range(segment_length): - result = powmod(result, 2, N) - return result == end_value - - def parallel_verify(self, y: mpz, proof: List[mpz]) -> bool: - """ - Performs parallel verification by checking each proof segment concurrently - using multiprocessing for true parallelism. - - Args: - y (mpz): The final VDF result to verify. - proof (List[mpz]): A list of intermediate values for parallel verification. - - Returns: - bool: True if verification succeeds, False otherwise. - """ - # Step 1: Prepare arguments for each segment verification - tasks = [ - (proof[i - 1] if i > 0 else self.x, proof[i], self.segment_length, self.N) - for i in range(len(proof)) - ] - - # Step 2: Use multiprocessing Pool to verify each segment in parallel - with Pool() as pool: - results = pool.map(self.verify_segment, tasks) - - # Step 3: Check if all segments verified successfully - if not all(results): - print("Parallel verification failed.") - return False - - # Final check: Verify that the last computed segment result matches y - return proof[-1] == y - - ##Private## - def _generate_rsa_modulus(self) -> Tuple[mpz, mpz, mpz]: - while True: - # Generate p and q with slightly fewer bits - p = gmpy2.next_prime(mpz_urandomb(self.rand, self.bit_size // 2 - 1)) - q = gmpy2.next_prime(mpz_urandomb(self.rand, self.bit_size // 2 - 1)) - - N = p * q - - # Check if N is within the desired bit size - if N.bit_length() <= self.bit_size: - return N, p, q - - def _generate_random_challenge(self) -> mpz: - return mpz_urandomb(self.rand, self.bit_size // 2) From e6d9b831c87733822b58a0914c1c4a827392db2c Mon Sep 17 00:00:00 2001 From: K <111819113+KennySwayzee93@users.noreply.github.com> Date: Fri, 28 Mar 2025 12:33:04 -0400 Subject: [PATCH 36/51] remove 0x --- puzzle-generator/src/converters/rsa_converter.py | 8 ++++---- .../src/converters/time_lock_puzzle_converter.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/puzzle-generator/src/converters/rsa_converter.py b/puzzle-generator/src/converters/rsa_converter.py index 8c5c1c3..209308c 100644 --- a/puzzle-generator/src/converters/rsa_converter.py +++ b/puzzle-generator/src/converters/rsa_converter.py @@ -18,8 +18,8 @@ def to_entity(rsa: RSA) -> RSAEntity: RSAEntity: The database entity """ return RSAEntity( - hex(rsa.get_p()), - hex(rsa.get_q()), - hex(rsa.get_N()), - hex(rsa.get_phi()), + hex(rsa.get_p())[2:], # remove 0x + hex(rsa.get_q())[2:], # remove 0x + hex(rsa.get_N())[2:], # remove 0x + hex(rsa.get_phi())[2:], # remove 0x ) diff --git a/puzzle-generator/src/converters/time_lock_puzzle_converter.py b/puzzle-generator/src/converters/time_lock_puzzle_converter.py index 7b789b5..4eb498d 100644 --- a/puzzle-generator/src/converters/time_lock_puzzle_converter.py +++ b/puzzle-generator/src/converters/time_lock_puzzle_converter.py @@ -21,9 +21,9 @@ def to_entity(puzzle: TimeLockPuzzle, rsa_id: str, y: MPZ) -> TimeLockPuzzleEnti TimeLockPuzzleEntity: The database entity """ return TimeLockPuzzleEntity( - x_hex=hex(puzzle.get_x()), - y_hex=hex(y), - t=str(puzzle.get_t()), - N_hex=hex(puzzle.get_N()), + x_hex=hex(puzzle.get_x())[2:], # remove 0x + y_hex=hex(y)[2:], # remove 0x + t=str(puzzle.get_t()), # remove 0x + N_hex=hex(puzzle.get_N())[2:], # remove 0x rsa_id=rsa_id, ) From 1a268d17ab0e39a819cd7ef600e04a9ea447a1aa Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 28 Mar 2025 12:47:14 -0400 Subject: [PATCH 37/51] updated --- docker-compose/mass-dev-docker-compose.yml | 32 ++++++------ orchestrator/docs/development.md | 2 +- orchestrator/package.json | 2 +- orchestrator/src/app.ts | 59 +++++++++------------- requester/docs/development.md | 2 +- requester/package.json | 2 +- requester/src/app.ts | 50 +++++++++++++++--- 7 files changed, 88 insertions(+), 61 deletions(-) diff --git a/docker-compose/mass-dev-docker-compose.yml b/docker-compose/mass-dev-docker-compose.yml index 5a689fc..670d5a2 100644 --- a/docker-compose/mass-dev-docker-compose.yml +++ b/docker-compose/mass-dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres3: condition: service_healthy @@ -138,7 +138,7 @@ services: retries: 5 orchestrator4: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres4: condition: service_healthy @@ -177,7 +177,7 @@ services: retries: 5 orchestrator5: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres5: condition: service_healthy @@ -216,7 +216,7 @@ services: retries: 5 orchestrator6: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres6: condition: service_healthy @@ -255,7 +255,7 @@ services: retries: 5 orchestrator7: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres7: condition: service_healthy @@ -294,7 +294,7 @@ services: retries: 5 orchestrator8: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres8: condition: service_healthy @@ -333,7 +333,7 @@ services: retries: 5 orchestrator9: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres9: condition: service_healthy @@ -372,7 +372,7 @@ services: retries: 5 orchestrator10: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres10: condition: service_healthy @@ -411,7 +411,7 @@ services: retries: 5 orchestrator11: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres11: condition: service_healthy @@ -450,7 +450,7 @@ services: retries: 5 orchestrator12: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres12: condition: service_healthy @@ -489,7 +489,7 @@ services: retries: 5 orchestrator13: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres13: condition: service_healthy @@ -528,7 +528,7 @@ services: retries: 5 orchestrator14: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres14: condition: service_healthy @@ -567,7 +567,7 @@ services: retries: 5 orchestrator15: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.92 depends_on: postgres15: condition: service_healthy @@ -601,7 +601,7 @@ services: - dbeaver-data:/opt/cloudbeaver/workspace requester: - image: randao/requester:v0.3.1 + image: randao/requester:v0.3.77 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index 83c8940..59a8612 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.3.45 # You can change this value to any version you want +export VERSION=v0.3.85 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/package.json b/orchestrator/package.json index e8f08fa..8421293 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "5.3.21", + "ao-process-clients": "5.4.9", "ao-vrf": "file:", "arweave": "^1.15.5", "aws-sdk": "^2.1692.0", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index e06bbc4..812c56c 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -6,53 +6,44 @@ import { dbConfig } from './db_config.js'; import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; import Arweave from 'arweave'; -// const RANDOM_CONFIG: RandomClientConfig = { -// wallet: JSON.parse(process.env.WALLET_JSON!), -// tokenProcessId: '', -// processId: '' -// } -//const randclient: IRandomClient = RandomClient.autoConfiguration() +const AO_CONFIG_BASE = { + MU_URL: "https://ur-mu.randao.net", + //MU_URL: "https://mu.ao-testnet.xyz", + GATEWAY_URL: "https://arweave.net", +}; + +const CU_URLS = [ + "https://ur-cu.randao.net", +]; + +// const CU_URLS = [ +// "https://cu.ao-testnet.xyz" +// ]; let randomClientInstance: RandomClient | null = null; async function getRandomClient(): Promise { + const randomIndex = Math.floor(Math.random() * CU_URLS.length); // Pick a random index + const AO_CONFIG = { + ...AO_CONFIG_BASE, + CU_URL: CU_URLS[randomIndex], // Select a random CU URL + }; + + console.log(`Using CU_URL: ${AO_CONFIG.CU_URL}`); // Log the selected CU URL + if (!randomClientInstance) { - const RANDOM_CONFIG: RandomClientConfig = await new RandomClientConfigBuilder() + randomClientInstance = ((await RandomClient.defaultBuilder()) + .withAOConfig(AO_CONFIG)) + .withProcessId("BPafv2apbvSU0SRZEksMULFtKQQb0KvS7PBTPadFVSQ") .withWallet(JSON.parse(process.env.WALLET_JSON!)) .build(); - - randomClientInstance = new RandomClient(RANDOM_CONFIG); } - return randomClientInstance; } -// async function getRandomClient(): Promise { -// if (!randomClientInstance) { -// const RANDOM_CONFIG: BaseClientConfigBuilder = await new BaseClientConfigBuilder() -// .withWallet(JSON.parse(process.env.WALLET_JSON!)) -// .withAOConfig({ -// CU_URL: "https://cu.randao.net", -// MODE: 'legacy' -// }) -// .build(); - -// randomClientInstance = new RandomClient(RANDOM_CONFIG); -// } - -// return randomClientInstance; -// } - -// async function getStakingClient(): Promise{ -// let test = await getProviderStakingClientAutoConfiguration() -// test.wallet = JSON.parse(process.env.WALLET_JSON!) -// const randclient = new ProviderStakingClient(test) -// return randclient -// } - const docker = new Docker(); // Constants for configuration -const POLLING_INTERVAL_MS = 1000; +const POLLING_INTERVAL_MS = 10000; const MINIMUM_ENTRIES = 1000; const DRYRUNTIMEOUT = 30000; // 30 seconds const MAX_OUTSTANDING_VDF_CONTAINERS = 10; diff --git a/requester/docs/development.md b/requester/docs/development.md index 5cb89cb..08e7c63 100644 --- a/requester/docs/development.md +++ b/requester/docs/development.md @@ -2,4 +2,4 @@ To build: Save all files Run: -docker build -t randao/requester:latest -t randao/requester:v0.1.5 . \ No newline at end of file +docker build -t randao/requester:latest -t randao/requester:v0.3.55 . \ No newline at end of file diff --git a/requester/package.json b/requester/package.json index 59e9853..982aeb7 100644 --- a/requester/package.json +++ b/requester/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "5.3.21", + "ao-process-clients": "5.4.9", "ao-vrf": "file:", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", diff --git a/requester/src/app.ts b/requester/src/app.ts index 2e369cb..5bd7c8f 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -9,7 +9,7 @@ import { RandomClientConfigBuilder, } from "ao-process-clients"; -const RETRY_DELAY_MS = 10000; // 10 seconds +const RETRY_DELAY_MS = 60000; // 60 seconds const PROVIDER_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes const PROVIDER_REQUEST_TIMEOUT = 60 * 1000; // 1 minute const CHANCE_TO_CALL_RANDOM = 1; @@ -17,14 +17,48 @@ const CHANCE_TO_CALL_RANDOM = 1; let cachedProviders: string[] = []; let lastProviderRefresh = 0; -async function getRandomClient(): Promise{ - // let test = await getRandomClientAutoConfiguration() - // test.wallet = JSON.parse(process.env.WALLET_JSON!) +const AO_CONFIG_BASE = { + MU_URL: "https://ur-mu.randao.net", + // MU_URL: "https://mu.ao-testnet.xyz", + GATEWAY_URL: "https://arweave.net", +}; + +const CU_URLS = [ + "https://ur-cu.randao.net", + //"https://cu2.randao.net", + //"https://cu3.randao.net", + //"https://cu4.randao.net", + //"https://cu5.randao.net", + //"https://cu6.randao.net:444" +]; + +// const CU_URLS = [ +// "https://cu.ao-testnet.xyz" +// ]; + +let randomClientInstance: RandomClient | null = null; + +async function getRandomClient(): Promise { + const randomIndex = Math.floor(Math.random() * CU_URLS.length); // Pick a random index + const AO_CONFIG = { + ...AO_CONFIG_BASE, + CU_URL: CU_URLS[randomIndex], // Select a random CU URL + }; + + console.log(`Using CU_URL: ${AO_CONFIG.CU_URL}`); // Log the selected CU URL - const RANDOM_CONFIG: RandomClientConfig = await new RandomClientConfigBuilder().withWallet(JSON.parse(process.env.REQUEST_WALLET_JSON!)).build() - const randclient = new RandomClient(RANDOM_CONFIG) - return randclient + if (!randomClientInstance) { + randomClientInstance = ((await RandomClient.defaultBuilder()) + .withAOConfig(AO_CONFIG)) + .withProcessId("BPafv2apbvSU0SRZEksMULFtKQQb0KvS7PBTPadFVSQ") + .withWallet(JSON.parse(process.env.REQUEST_WALLET_JSON!)) + .build(); } + return randomClientInstance; +} + + + let totalRandomCalled = 0; let totalTimeToFulfill = 0; let fulfilledRequests = 0; @@ -54,6 +88,7 @@ async function getRandomProviders(randclient: RandomClient): Promise<{ providers // Create the actual provider fetch promise const fetchPromise = async () => { const providerInfo = await randclient.getAllProviderActivity(); + console.log(providerInfo) const eligibleProviders = providerInfo //@ts-ignore .filter(provider => provider.active === 1) @@ -115,6 +150,7 @@ async function main() { const { providers, count } = await getRandomProviders(randclient); console.log(`Selected ${count} providers:`, providers); await randclient.createRequest(providers, count, callbackId); + //await randclient.createRequest(["X1tqliRkKnClhVQ4aIeyuOaPTzr5PfnxqAoSdpTzZy8"], 1, "123"); totalRandomCalled++; console.log("Random request initiated. Awaiting request ID in open requests..."); } From b0b39e7fa1b2bf556aab94864109072c539f78fe Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 28 Mar 2025 12:48:22 -0400 Subject: [PATCH 38/51] updated --- docker-compose/dev-docker-compose.yml | 6 ++--- docker-compose/docker-compose.yml | 2 +- docker-compose/mass-dev-docker-compose.yml | 30 +++++++++++----------- terraform/ecs.tf | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docker-compose/dev-docker-compose.yml b/docker-compose/dev-docker-compose.yml index 4833642..8a6f997 100644 --- a/docker-compose/dev-docker-compose.yml +++ b/docker-compose/dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.99 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.99 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.99 depends_on: postgres3: condition: service_healthy diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index ae00b9e..e08beb0 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.3.45 + image: randao/orchestrator:v0.3.99 depends_on: postgres: condition: service_healthy diff --git a/docker-compose/mass-dev-docker-compose.yml b/docker-compose/mass-dev-docker-compose.yml index 670d5a2..c8acec0 100644 --- a/docker-compose/mass-dev-docker-compose.yml +++ b/docker-compose/mass-dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres3: condition: service_healthy @@ -138,7 +138,7 @@ services: retries: 5 orchestrator4: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres4: condition: service_healthy @@ -177,7 +177,7 @@ services: retries: 5 orchestrator5: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres5: condition: service_healthy @@ -216,7 +216,7 @@ services: retries: 5 orchestrator6: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres6: condition: service_healthy @@ -255,7 +255,7 @@ services: retries: 5 orchestrator7: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres7: condition: service_healthy @@ -294,7 +294,7 @@ services: retries: 5 orchestrator8: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres8: condition: service_healthy @@ -333,7 +333,7 @@ services: retries: 5 orchestrator9: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres9: condition: service_healthy @@ -372,7 +372,7 @@ services: retries: 5 orchestrator10: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres10: condition: service_healthy @@ -411,7 +411,7 @@ services: retries: 5 orchestrator11: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres11: condition: service_healthy @@ -450,7 +450,7 @@ services: retries: 5 orchestrator12: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres12: condition: service_healthy @@ -489,7 +489,7 @@ services: retries: 5 orchestrator13: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres13: condition: service_healthy @@ -528,7 +528,7 @@ services: retries: 5 orchestrator14: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres14: condition: service_healthy @@ -567,7 +567,7 @@ services: retries: 5 orchestrator15: - image: randao/orchestrator:v0.3.92 + image: randao/orchestrator:v0.3.99 depends_on: postgres15: condition: service_healthy diff --git a/terraform/ecs.tf b/terraform/ecs.tf index ccd5cee..e89e550 100644 --- a/terraform/ecs.tf +++ b/terraform/ecs.tf @@ -62,7 +62,7 @@ resource "aws_ecs_task_definition" "orchestrator_service" { container_definitions = jsonencode([ { name = "orchestrator" - image = "randao/orchestrator:v0.3.45" + image = "randao/orchestrator:v0.3.99" environment = [ { name = "ENVIRONMENT" From 40d7169e30875d2b5537ee5588232ca4aaf7e386 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 28 Mar 2025 18:04:39 -0400 Subject: [PATCH 39/51] done --- docker-compose/dev-docker-compose.yml | 6 +- docker-compose/docker-compose.yml | 2 +- docker-compose/mass-dev-docker-compose.yml | 34 +-- orchestrator/docs/development.md | 2 +- orchestrator/package.json | 2 +- orchestrator/src/app.ts | 287 ++++++++++-------- orchestrator/src/ecs_config.ts | 2 +- puzzle-generator/Dockerfile | 2 +- puzzle-generator/docs/developing.md | 17 +- .../src/database/entity/RSAEntity.py | 4 +- .../database/entity/TimeLockPuzzleEntity.py | 4 +- requester/docs/development.md | 2 +- requester/package.json | 2 +- requester/src/app.ts | 28 +- terraform/ecs.tf | 4 +- 15 files changed, 211 insertions(+), 187 deletions(-) diff --git a/docker-compose/dev-docker-compose.yml b/docker-compose/dev-docker-compose.yml index 8a6f997..2b15562 100644 --- a/docker-compose/dev-docker-compose.yml +++ b/docker-compose/dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres3: condition: service_healthy diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index e08beb0..74fb28f 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres: condition: service_healthy diff --git a/docker-compose/mass-dev-docker-compose.yml b/docker-compose/mass-dev-docker-compose.yml index c8acec0..c69da7e 100644 --- a/docker-compose/mass-dev-docker-compose.yml +++ b/docker-compose/mass-dev-docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: # Instance 1 postgres1: @@ -21,7 +19,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres1: condition: service_healthy @@ -60,7 +58,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres2: condition: service_healthy @@ -99,7 +97,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres3: condition: service_healthy @@ -138,7 +136,7 @@ services: retries: 5 orchestrator4: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres4: condition: service_healthy @@ -177,7 +175,7 @@ services: retries: 5 orchestrator5: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres5: condition: service_healthy @@ -216,7 +214,7 @@ services: retries: 5 orchestrator6: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres6: condition: service_healthy @@ -255,7 +253,7 @@ services: retries: 5 orchestrator7: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres7: condition: service_healthy @@ -294,7 +292,7 @@ services: retries: 5 orchestrator8: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres8: condition: service_healthy @@ -333,7 +331,7 @@ services: retries: 5 orchestrator9: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres9: condition: service_healthy @@ -372,7 +370,7 @@ services: retries: 5 orchestrator10: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres10: condition: service_healthy @@ -411,7 +409,7 @@ services: retries: 5 orchestrator11: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres11: condition: service_healthy @@ -450,7 +448,7 @@ services: retries: 5 orchestrator12: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres12: condition: service_healthy @@ -489,7 +487,7 @@ services: retries: 5 orchestrator13: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres13: condition: service_healthy @@ -528,7 +526,7 @@ services: retries: 5 orchestrator14: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres14: condition: service_healthy @@ -567,7 +565,7 @@ services: retries: 5 orchestrator15: - image: randao/orchestrator:v0.3.99 + image: randao/orchestrator:v0.4.2 depends_on: postgres15: condition: service_healthy @@ -601,7 +599,7 @@ services: - dbeaver-data:/opt/cloudbeaver/workspace requester: - image: randao/requester:v0.3.77 + image: randao/requester:v0.4.0 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index 59a8612..82b5b5a 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.3.85 # You can change this value to any version you want +export VERSION=v0.4.2 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/package.json b/orchestrator/package.json index 8421293..2a04907 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "5.4.9", + "ao-process-clients": "^5.4.16", "ao-vrf": "file:", "arweave": "^1.15.5", "aws-sdk": "^2.1692.0", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 812c56c..78763ba 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -6,34 +6,19 @@ import { dbConfig } from './db_config.js'; import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; import Arweave from 'arweave'; -const AO_CONFIG_BASE = { +const AO_CONFIG = { MU_URL: "https://ur-mu.randao.net", - //MU_URL: "https://mu.ao-testnet.xyz", + CU_URL: "https://ur-cu.randao.net", GATEWAY_URL: "https://arweave.net", }; -const CU_URLS = [ - "https://ur-cu.randao.net", -]; - -// const CU_URLS = [ -// "https://cu.ao-testnet.xyz" -// ]; let randomClientInstance: RandomClient | null = null; async function getRandomClient(): Promise { - const randomIndex = Math.floor(Math.random() * CU_URLS.length); // Pick a random index - const AO_CONFIG = { - ...AO_CONFIG_BASE, - CU_URL: CU_URLS[randomIndex], // Select a random CU URL - }; - - console.log(`Using CU_URL: ${AO_CONFIG.CU_URL}`); // Log the selected CU URL - + if (!randomClientInstance) { randomClientInstance = ((await RandomClient.defaultBuilder()) .withAOConfig(AO_CONFIG)) - .withProcessId("BPafv2apbvSU0SRZEksMULFtKQQb0KvS7PBTPadFVSQ") .withWallet(JSON.parse(process.env.WALLET_JSON!)) .build(); } @@ -50,7 +35,7 @@ const MAX_OUTSTANDING_VDF_CONTAINERS = 10; const RANDOM_PER_VDF = 10; const MAX_RETRIES = 10; const RETRY_DELAY_MS = 10000; -const VDF_JOB_IMAGE = 'randao/vdf_job:v0.1.4'; +const VDF_JOB_IMAGE = 'randao/puzzle-gen:v0.1.1'; const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); const ongoingContainers = new Set(); // Track container IDs of running Docker containers @@ -107,27 +92,44 @@ async function connectWithRetry(): Promise { throw new Error("Failed to connect to PostgreSQL after multiple attempts"); } -// Setup the `verifiable_delay_functions` table if not exists +// Function to initialize the PostgreSQL database schema async function setupDatabase(client: Client): Promise { - await client.query(` - CREATE TABLE IF NOT EXISTS verifiable_delay_functions ( - id TEXT PRIMARY KEY, - request_id TEXT, - modulus TEXT NOT NULL, - input TEXT NOT NULL, - output TEXT NOT NULL, - proof JSON NOT NULL, - date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - detected_completed TIMESTAMP NULL - ); - `); + try { + // Drop old tables maybe + await client.query(` + DROP TABLE IF EXISTS verifiable_delay_functions CASCADE; + `); - // Ensure detected_completed column exists in case the table was created before it was added - await client.query(` - ALTER TABLE verifiable_delay_functions - ADD COLUMN IF NOT EXISTS detected_completed TIMESTAMP NULL; + // Create the rsa_keys table + await client.query(` + CREATE TABLE rsa_keys ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + p TEXT NOT NULL, + q TEXT NOT NULL, + modulus TEXT NOT NULL UNIQUE, -- Store hex string of modulus N + phi TEXT NOT NULL + ); `); - console.log("Database setup complete. 'verifiable_delay_functions' table is ready."); + + // Create the time_lock_puzzles table with relation to rsa_keys based on rsa_id + await client.query(` +CREATE TABLE time_lock_puzzles ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + x TEXT NOT NULL, + y TEXT NOT NULL, + t TEXT NOT NULL, + modulus TEXT NOT NULL, -- Store hex string of modulus N + request_id TEXT NULL, + rsa_id UUID NOT NULL UNIQUE, + detected_completed TIMESTAMP NULL, -- Added to track completion time + FOREIGN KEY (rsa_id) REFERENCES rsa_keys(id) ON DELETE CASCADE +); + `); + + console.log("✅ Database setup complete. Tables are properly linked on rsa_id."); + } catch (error) { + console.error("❌ Error setting up database:", error); + } } @@ -164,11 +166,11 @@ async function triggerVDFJobPod(): Promise { } else { // Check if we have reached the maximum number of containers if (ongoingContainers.size >= MAX_OUTSTANDING_VDF_CONTAINERS) { - console.log(`Maximum outstanding VDF containers (${MAX_OUTSTANDING_VDF_CONTAINERS}) reached. Not starting new container.`); + console.log(`Maximum outstanding puzzle-gen containers (${MAX_OUTSTANDING_VDF_CONTAINERS}) reached. Not starting new container.`); return null; } - const containerName = `vdf_job_${Date.now()}_${Math.floor(Math.random() * 100000)}`; + const containerName = `puzzle-gen_job_${Date.now()}_${Math.floor(Math.random() * 100000)}`; console.log(`Starting Docker container with name: ${containerName}`); try { if (!pulledDockerimage) { @@ -188,7 +190,7 @@ async function triggerVDFJobPod(): Promise { } const container = await docker.createContainer({ Image: VDF_JOB_IMAGE, - Cmd: ['sh', '-c', `for i in $(seq 1 ${RANDOM_PER_VDF}); do python main.py; done`], + Cmd: ['sh', '-c', `python3 main.py ${RANDOM_PER_VDF}`], Env: [ `DATABASE_TYPE=postgresql`, `DATABASE_HOST=${dbConfig.host}`, @@ -293,7 +295,7 @@ function isDockerError(error: unknown): error is { statusCode: number } { // Function to process hex output for 64-bit modulus function hexMod64Bit(expectedOutput: string): { expectedOutput64BitBase10: string } { // Parse the hexadecimal string into a BigInt - const number = BigInt(`0x${expectedOutput}`); + const number = BigInt(`${expectedOutput}`); // Define the 64-bit modulus (2^64 - 1) const modulus = BigInt("0x7FFFFFFFF"); @@ -310,11 +312,6 @@ function hexMod64Bit(expectedOutput: string): { expectedOutput64BitBase10: strin }; } -// Function to prepend 0x to hex strings -function addHexPrefix(value: string): string { - return value.startsWith('0x') ? value : `0x${value}`; -} - function updateAvailableValuesAsync(currentCount: number) { return (async () => { try { @@ -383,7 +380,7 @@ async function checkAndFetchIfNeeded(client: Client) { const on_chain_avalible_random = await getProviderAvailableRandomValues(PROVIDER_ID); // Query current count of usable DB entries const res = await client.query( - 'SELECT COUNT(*) AS count FROM verifiable_delay_functions WHERE request_id IS NULL' + 'SELECT COUNT(*) AS count FROM time_lock_puzzles WHERE request_id IS NULL' ); const currentCount = parseInt(res.rows[0].count, 10); console.log("Total usable DB entries: " + currentCount); @@ -439,8 +436,8 @@ async function fulfillRandomChallenge(client: Client, requestId: string, parentL try { // Fetch the necessary details from the database using requestId const res = await client.query( - `SELECT id, modulus, input - FROM verifiable_delay_functions + `SELECT id, modulus, x + FROM time_lock_puzzles WHERE request_id = $1`, [requestId] ); @@ -450,16 +447,18 @@ async function fulfillRandomChallenge(client: Client, requestId: string, parentL return; } - const { id: dbId, modulus, input } = res.rows[0]; + const { id: dbId, modulus, x: input } = res.rows[0]; console.log(`${parentLogId} Fetched entry details - Request ID: ${requestId}, DB ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); - // Add hex prefix to modulus and input - const hexModulus = addHexPrefix(modulus); - const hexInput = addHexPrefix(input); - console.log(`${parentLogId} Posting VDF challenge for Request ID: ${requestId}, DB ID: ${dbId}`); - await (await getRandomClient()).postVDFChallenge(requestId, hexModulus, hexInput); + await (await getRandomClient()).commit({ + requestId: requestId, + puzzle: { + input: input, + modulus: modulus + } + }); console.log(`${parentLogId} Challenge posted for Request ID: ${requestId}. Waiting to post proof...`); } catch (error) { console.error(`${parentLogId} Error posting VDF challenge for Request ID: ${requestId}:`, error); @@ -472,27 +471,39 @@ async function fulfillRandomChallenge(client: Client, requestId: string, parentL async function fulfillRandomOutput(client: Client, requestId: string, parentLogId: string): Promise { try { // Fetch the output and proof from the database using the requestId - const res = await client.query('SELECT id, output, proof FROM verifiable_delay_functions WHERE request_id = $1', [requestId]); + const res = await client.query( + `SELECT + tlp.id, + tlp.y AS output, + rk.p, + rk.q + FROM time_lock_puzzles tlp + JOIN rsa_keys rk ON tlp.rsa_id = rk.id + WHERE tlp.request_id = $1`, + [requestId] + ); + if (!res.rowCount) { console.error(`No entry found for request ID: ${requestId}`); return; } - const { id: dbId, output, proof } = res.rows[0]; - - console.log(`${parentLogId} Fetched entry from database for output - ID: ${dbId}, requestID: ${requestId}`); - - // Process the output through hexMod64Bit - const processedOutput = hexMod64Bit(output).expectedOutput64BitBase10; - console.log(`${parentLogId} Processed output: ${processedOutput} For request ID: ${requestId}`); - // Process the proof array - add hex prefix to each element - let processedProof = proof; - if (Array.isArray(proof)) { - processedProof = proof.map(element => addHexPrefix(element)); - } - const proofString = JSON.stringify(processedProof); + // Map the response to structured variables +const { + id: dbId, + output, // Mapping 'y' to 'output' + p: rsaP, + q: rsaQ +} = res.rows[0]; + console.log(`${parentLogId} Fetched entry from database for output - ID: ${dbId}, requestID: ${requestId}, output: ${output}, rsaP: ${rsaP}, rsaQ ${rsaQ} `); console.log(`${parentLogId} Posting VDF output and proof for - ID: ${dbId}, request ID: ${requestId}`); - await (await getRandomClient()).postVDFOutputAndProof(requestId, processedOutput, proofString); + await (await getRandomClient()).reveal({ + requestId: requestId, + rsa_key: { + p: rsaP, + q: rsaQ + } + }) console.log(`${parentLogId} Proof posted for request ID: ${requestId}`); } catch (error) { console.error(`${parentLogId} Error fulfilling random output for request ID: ${requestId}:`, error); @@ -631,7 +642,9 @@ async function polling(client: any) { (async () => { const s4 = Date.now(); console.log(`${logId} Step 4 started.`); - await cleanupFulfilledEntries(client, openRequests, logId); + + //TODO enable this again later + //await cleanupFulfilledEntries(client, openRequests, logId); stepTracking.step4 = { completed: true, timeTaken: Date.now() - s4 }; console.log(`${logId} Step 4 completed. Time taken: ${stepTracking.step4.timeTaken}ms`); })(), @@ -667,77 +680,87 @@ async function processChallengeRequests( try { await client.query('BEGIN'); // Start transaction - + console.log(`${parentLogId} Fetching existing request mappings.`); + // Fetch already assigned request_id -> dbId mappings const existingMappingsRes = await client.query( - `SELECT request_id FROM verifiable_delay_functions + `SELECT request_id FROM time_lock_puzzles WHERE request_id = ANY($1) FOR UPDATE SKIP LOCKED`, [requestIds] ); - + const existingRequestIds = new Set(existingMappingsRes.rows.map(row => row.request_id)); console.log(`${parentLogId} Found ${existingRequestIds.size} already mapped requests.`); - + // Find only the unmapped requests (requestIds not in existingRequestIds) const unmappedRequestIds = requestIds.filter(requestId => !existingRequestIds.has(requestId)); console.log(`${parentLogId} Unmapped requests: ${unmappedRequestIds.length}`); - - // Fetch available DB entries for unmapped requests - console.log(`${parentLogId} Fetching available DB entries.`); - const dbRes = await client.query( - `SELECT id FROM verifiable_delay_functions - WHERE request_id IS NULL - ORDER BY id ASC - LIMIT $1 - FOR UPDATE SKIP LOCKED`, - [unmappedRequestIds.length] - ); - - const availableDbEntries = dbRes.rows.map(row => row.id); - console.log(`${parentLogId} Found ${availableDbEntries.length} available DB entries.`); - - // Reduce request list if we don’t have enough DB entries - if (availableDbEntries.length < unmappedRequestIds.length) { - console.log(`${parentLogId} Limiting requests to ${availableDbEntries.length} due to DB availability.`); - unmappedRequestIds.length = availableDbEntries.length; + + let mappedEntries: { requestId: string, dbId: number }[] = []; + + if (unmappedRequestIds.length > 0) { + console.log(`${parentLogId} Fetching available DB entries.`); + const dbRes = await client.query( + `SELECT id FROM time_lock_puzzles + WHERE request_id IS NULL + ORDER BY id ASC + LIMIT $1 + FOR UPDATE SKIP LOCKED`, + [unmappedRequestIds.length] + ); + + const availableDbEntries = dbRes.rows.map(row => row.id); + console.log(`${parentLogId} Found ${availableDbEntries.length} available DB entries.`); + + if (availableDbEntries.length > 0) { + const numMappings = Math.min(unmappedRequestIds.length, availableDbEntries.length); + + for (let i = 0; i < numMappings; i++) { + await client.query( + `UPDATE time_lock_puzzles + SET request_id = $1 + WHERE id = $2`, + [unmappedRequestIds[i], availableDbEntries[i]] + ); + mappedEntries.push({ requestId: unmappedRequestIds[i], dbId: availableDbEntries[i] }); + console.log(`${parentLogId} Assigned Request ID ${unmappedRequestIds[i]} to DB Entry ${availableDbEntries[i]}.`); + } + } else { + console.log(`${parentLogId} No available DB entries for unmapped requests.`); + } } - - if (availableDbEntries.length === 0 && existingRequestIds.size === 0) { - console.log(`${parentLogId} No available DB entries to process and no existing mappings.`); - await client.query('COMMIT'); // Commit to release locks + + // Collect all request IDs (previously mapped + newly mapped) + const allRequestIds = [...existingRequestIds, ...mappedEntries.map(entry => entry.requestId)]; + + if (allRequestIds.length === 0) { + console.log(`${parentLogId} No requests to process. Committing transaction.`); + await client.query('COMMIT'); return; } - - // Map unmapped requestIds to available DB entries (1:1) - for (let i = 0; i < unmappedRequestIds.length; i++) { - await client.query( - `UPDATE verifiable_delay_functions - SET request_id = $1 - WHERE id = $2`, - [unmappedRequestIds[i], availableDbEntries[i]] - ); - console.log(`${parentLogId} Assigned Request ID ${unmappedRequestIds[i]} to DB Entry ${availableDbEntries[i]}.`); - } - + await client.query('COMMIT'); // Commit all updates at once - - // Call fulfillRandomChallenge for all request IDs (existing + newly mapped) - // Create an array of promises and use Promise.all to await them all in parallel - const promises = requestIds.map(requestId => - fulfillRandomChallenge(client, requestId, parentLogId) - .catch(error => console.error(`${parentLogId} Error fulfilling challenge for Request ID ${requestId}:`, error)) + console.log(`${parentLogId} Committed all changes. Now fulfilling challenges.`); + + // Call fulfillRandomChallenge for all request IDs + await Promise.all( + allRequestIds.map(requestId => + fulfillRandomChallenge(client, requestId, parentLogId) + .catch(error => console.error(`${parentLogId} Error fulfilling challenge for Request ID ${requestId}:`, error)) + ) ); - - await Promise.all(promises); // Wait for all promises to resolve + console.log(`${parentLogId} All challenges fulfilled`); - - console.log(`${parentLogId} Step 2 completed.`); - } catch (error) { + } catch (error:any) { console.error(`${parentLogId} Error in processChallengeRequests:`, error); await client.query('ROLLBACK'); // Rollback on failure + + console.error(`SQL State: ${error.code}, Message: ${error.message}`); } + + } // Step 3: Process Output Requests (unchanged but with logging) @@ -782,7 +805,7 @@ async function cleanupFulfilledEntries( // Fetch all entries with a request_id const result = await client.query(` SELECT id, request_id, detected_completed - FROM verifiable_delay_functions + FROM time_lock_puzzles WHERE request_id IS NOT NULL `); @@ -810,20 +833,28 @@ async function cleanupFulfilledEntries( // Mark entries as completed if (markAsCompleted.length > 0) { await client.query(` - UPDATE verifiable_delay_functions + UPDATE time_lock_puzzles SET detected_completed = NOW() WHERE id = ANY($1) `, [markAsCompleted]); console.log(`${parentLogId} Marked ${markAsCompleted.length} entries as completed.`); } - // Delete old completed entries + // Delete old completed entries //TODO make sure its cleaning up BOTH tables if (markForDeletion.length > 0) { await client.query(` - DELETE FROM verifiable_delay_functions - WHERE id = ANY($1) + DELETE FROM rsa_keys + WHERE id IN ( + SELECT rsa_id FROM time_lock_puzzles WHERE id = ANY($1) + ); + `, [markForDeletion]); + + await client.query(` + DELETE FROM time_lock_puzzles + WHERE id = ANY($1); `, [markForDeletion]); - console.log(`${parentLogId} Deleted ${markForDeletion.length} old completed entries.`); + + console.log(`${parentLogId} Deleted ${markForDeletion.length} old completed entries and corresponding RSA keys.`); } await client.query('COMMIT'); @@ -849,7 +880,7 @@ async function run(): Promise { }); setInterval(async () => { - const res = await client.query('SELECT COUNT(*) as count FROM verifiable_delay_functions'); + const res = await client.query('SELECT COUNT(*) as count FROM time_lock_puzzles'); console.log(`Periodic log - Current database size: ${res.rows[0].count}`); // Check and fetch entries for the database if needed console.log("Step 0: Checking and fetching database entries if below threshold."); diff --git a/orchestrator/src/ecs_config.ts b/orchestrator/src/ecs_config.ts index e97184a..5bd863e 100644 --- a/orchestrator/src/ecs_config.ts +++ b/orchestrator/src/ecs_config.ts @@ -60,7 +60,7 @@ export async function launchVDFTask( containerOverrides: [ { name: 'vdf_job_container', // Must match the container name in the task definition - command: ['sh', '-c', `for i in $(seq 1 ${random_per_vdf}); do python main.py; done`], + command: ['sh', '-c', `python3 main.py ${random_per_vdf}`], }, ], }, diff --git a/puzzle-generator/Dockerfile b/puzzle-generator/Dockerfile index 9a05905..be0ffdf 100644 --- a/puzzle-generator/Dockerfile +++ b/puzzle-generator/Dockerfile @@ -1,5 +1,5 @@ # Use an official Python image as a base -FROM python:3.8-slim +FROM python:3.12 # Install system dependencies needed for gmpy2 and PostgreSQL connection RUN apt-get update && apt-get install -y \ diff --git a/puzzle-generator/docs/developing.md b/puzzle-generator/docs/developing.md index 6cd1ee3..d15c15f 100644 --- a/puzzle-generator/docs/developing.md +++ b/puzzle-generator/docs/developing.md @@ -52,4 +52,19 @@ pytest With coverage: ```bash pytest --cov=src -``` \ No newline at end of file +``` + + + + + + +# Build the Docker image with the version tag +docker build -t randao/puzzle-gen:latest -t randao/puzzle-gen:v0.1.1 . + +# Log in to Docker +docker login + +# Push the image with the version tag +docker push randao/puzzle-gen:latest +docker push randao/puzzle-gen:v0.1.1 \ No newline at end of file diff --git a/puzzle-generator/src/database/entity/RSAEntity.py b/puzzle-generator/src/database/entity/RSAEntity.py index b2e8b79..7ddb16f 100644 --- a/puzzle-generator/src/database/entity/RSAEntity.py +++ b/puzzle-generator/src/database/entity/RSAEntity.py @@ -18,7 +18,7 @@ class RSAEntity(Base, Saveable): id = Column(String, primary_key=True) # Unique generated string ID p = Column(String, nullable=False) # Store hex string of prime p q = Column(String, nullable=False) # Store hex string of prime q - N = Column(String, nullable=False) # Store hex string of modulus N + modulus = Column(String, nullable=False) # Store hex string of modulus N phi = Column(String, nullable=False) # Store hex string of Euler's totient puzzle = relationship( "TimeLockPuzzleEntity", back_populates="rsa", uselist=False @@ -39,5 +39,5 @@ def __init__(self, p_hex: str, q_hex: str, N_hex: str, phi_hex: str): self.id = str(uuid.uuid4()) # Generate ID on creation self.p = p_hex self.q = q_hex - self.N = N_hex + self.modulus = N_hex self.phi = phi_hex diff --git a/puzzle-generator/src/database/entity/TimeLockPuzzleEntity.py b/puzzle-generator/src/database/entity/TimeLockPuzzleEntity.py index 2abb780..487f5db 100644 --- a/puzzle-generator/src/database/entity/TimeLockPuzzleEntity.py +++ b/puzzle-generator/src/database/entity/TimeLockPuzzleEntity.py @@ -20,7 +20,7 @@ class TimeLockPuzzleEntity(Base, Saveable): x = Column(String, nullable=False) # Store hex string of input value x y = Column(String, nullable=False) # Store hex string of y value t = Column(String, nullable=False) # Store base 10 string of time parameter t - N = Column(String, nullable=False) # Store hex string of modulus N + modulus = Column(String, nullable=False) # Store hex string of modulus N request_id = Column( String, nullable=True ) # Optional associated randomness request id (will be filled within the provider node runtime) @@ -45,5 +45,5 @@ def __init__(self, x_hex: str, y_hex: str, t: str, N_hex: str, rsa_id: str): self.x = x_hex self.y = y_hex self.t = t - self.N = N_hex + self.modulus = N_hex self.rsa_id = rsa_id diff --git a/requester/docs/development.md b/requester/docs/development.md index 08e7c63..a275bc2 100644 --- a/requester/docs/development.md +++ b/requester/docs/development.md @@ -2,4 +2,4 @@ To build: Save all files Run: -docker build -t randao/requester:latest -t randao/requester:v0.3.55 . \ No newline at end of file +docker build -t randao/requester:latest -t randao/requester:v0.4.0 . \ No newline at end of file diff --git a/requester/package.json b/requester/package.json index 982aeb7..4d7aa9a 100644 --- a/requester/package.json +++ b/requester/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "5.4.9", + "ao-process-clients": "^5.4.16", "ao-vrf": "file:", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", diff --git a/requester/src/app.ts b/requester/src/app.ts index 5bd7c8f..74feb04 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -9,7 +9,7 @@ import { RandomClientConfigBuilder, } from "ao-process-clients"; -const RETRY_DELAY_MS = 60000; // 60 seconds +const RETRY_DELAY_MS = 2000; // 2 seconds const PROVIDER_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes const PROVIDER_REQUEST_TIMEOUT = 60 * 1000; // 1 minute const CHANCE_TO_CALL_RANDOM = 1; @@ -17,40 +17,20 @@ const CHANCE_TO_CALL_RANDOM = 1; let cachedProviders: string[] = []; let lastProviderRefresh = 0; -const AO_CONFIG_BASE = { +const AO_CONFIG = { MU_URL: "https://ur-mu.randao.net", - // MU_URL: "https://mu.ao-testnet.xyz", + Cu_URL: "https://ur-cu.randao.net", GATEWAY_URL: "https://arweave.net", }; -const CU_URLS = [ - "https://ur-cu.randao.net", - //"https://cu2.randao.net", - //"https://cu3.randao.net", - //"https://cu4.randao.net", - //"https://cu5.randao.net", - //"https://cu6.randao.net:444" -]; - -// const CU_URLS = [ -// "https://cu.ao-testnet.xyz" -// ]; - let randomClientInstance: RandomClient | null = null; async function getRandomClient(): Promise { - const randomIndex = Math.floor(Math.random() * CU_URLS.length); // Pick a random index - const AO_CONFIG = { - ...AO_CONFIG_BASE, - CU_URL: CU_URLS[randomIndex], // Select a random CU URL - }; - - console.log(`Using CU_URL: ${AO_CONFIG.CU_URL}`); // Log the selected CU URL if (!randomClientInstance) { randomClientInstance = ((await RandomClient.defaultBuilder()) .withAOConfig(AO_CONFIG)) - .withProcessId("BPafv2apbvSU0SRZEksMULFtKQQb0KvS7PBTPadFVSQ") + .withProcessId("2ExUldxQ5NA_hnElSWYq0_lCBgeQQPxPhFbWDFihDEY") .withWallet(JSON.parse(process.env.REQUEST_WALLET_JSON!)) .build(); } diff --git a/terraform/ecs.tf b/terraform/ecs.tf index e89e550..b2c8d5a 100644 --- a/terraform/ecs.tf +++ b/terraform/ecs.tf @@ -62,7 +62,7 @@ resource "aws_ecs_task_definition" "orchestrator_service" { container_definitions = jsonencode([ { name = "orchestrator" - image = "randao/orchestrator:v0.3.99" + image = "randao/orchestrator:v0.4.2" environment = [ { name = "ENVIRONMENT" @@ -144,7 +144,7 @@ resource "aws_ecs_task_definition" "vdf_job" { container_definitions = jsonencode([ { name = "vdf_job_container" - image = "randao/vdf_job:v0.1.4" + image = "randao/puzzle-gen:v0.1.1" command = ["python", "main.py"], environment = [ { From 24b0f5b032b107ae725d386fe5b2684848a9a3a5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 31 Mar 2025 11:04:09 -0400 Subject: [PATCH 40/51] newer random --- docker-compose/dev-docker-compose.yml | 6 +-- docker-compose/docker-compose.yml | 2 +- docker-compose/mass-dev-docker-compose.yml | 32 ++++++------ orchestrator/docs/development.md | 2 +- orchestrator/src/app.ts | 59 ++++++++-------------- requester/src/app.ts | 2 +- terraform/ecs.tf | 2 +- 7 files changed, 44 insertions(+), 61 deletions(-) diff --git a/docker-compose/dev-docker-compose.yml b/docker-compose/dev-docker-compose.yml index 2b15562..5b5d1ad 100644 --- a/docker-compose/dev-docker-compose.yml +++ b/docker-compose/dev-docker-compose.yml @@ -21,7 +21,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres1: condition: service_healthy @@ -60,7 +60,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres2: condition: service_healthy @@ -99,7 +99,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres3: condition: service_healthy diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 74fb28f..120c8a8 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres: condition: service_healthy diff --git a/docker-compose/mass-dev-docker-compose.yml b/docker-compose/mass-dev-docker-compose.yml index c69da7e..f2c0528 100644 --- a/docker-compose/mass-dev-docker-compose.yml +++ b/docker-compose/mass-dev-docker-compose.yml @@ -19,7 +19,7 @@ services: retries: 5 orchestrator1: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres1: condition: service_healthy @@ -58,7 +58,7 @@ services: retries: 5 orchestrator2: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres2: condition: service_healthy @@ -97,7 +97,7 @@ services: retries: 5 orchestrator3: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres3: condition: service_healthy @@ -136,7 +136,7 @@ services: retries: 5 orchestrator4: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres4: condition: service_healthy @@ -175,7 +175,7 @@ services: retries: 5 orchestrator5: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres5: condition: service_healthy @@ -214,7 +214,7 @@ services: retries: 5 orchestrator6: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres6: condition: service_healthy @@ -253,7 +253,7 @@ services: retries: 5 orchestrator7: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres7: condition: service_healthy @@ -292,7 +292,7 @@ services: retries: 5 orchestrator8: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres8: condition: service_healthy @@ -331,7 +331,7 @@ services: retries: 5 orchestrator9: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres9: condition: service_healthy @@ -370,7 +370,7 @@ services: retries: 5 orchestrator10: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres10: condition: service_healthy @@ -409,7 +409,7 @@ services: retries: 5 orchestrator11: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres11: condition: service_healthy @@ -448,7 +448,7 @@ services: retries: 5 orchestrator12: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres12: condition: service_healthy @@ -487,7 +487,7 @@ services: retries: 5 orchestrator13: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres13: condition: service_healthy @@ -526,7 +526,7 @@ services: retries: 5 orchestrator14: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres14: condition: service_healthy @@ -565,7 +565,7 @@ services: retries: 5 orchestrator15: - image: randao/orchestrator:v0.4.2 + image: randao/orchestrator:v0.4.55 depends_on: postgres15: condition: service_healthy @@ -599,7 +599,7 @@ services: - dbeaver-data:/opt/cloudbeaver/workspace requester: - image: randao/requester:v0.4.0 + image: randao/requester:v0.4.2 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index 82b5b5a..d7f3b1b 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.4.2 # You can change this value to any version you want +export VERSION=v0.4.55 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 78763ba..77f3ad5 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -1,7 +1,7 @@ import { Client } from 'pg'; import Docker from 'dockerode'; import AWS from 'aws-sdk'; -import { BaseClientConfig, BaseClientConfigBuilder, GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, RandomClient, RandomClientConfig, RandomClientConfigBuilder} from "ao-process-clients" +import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, RandomClient} from "ao-process-clients" import { dbConfig } from './db_config.js'; import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; import Arweave from 'arweave'; @@ -31,11 +31,9 @@ const docker = new Docker(); const POLLING_INTERVAL_MS = 10000; const MINIMUM_ENTRIES = 1000; const DRYRUNTIMEOUT = 30000; // 30 seconds -const MAX_OUTSTANDING_VDF_CONTAINERS = 10; -const RANDOM_PER_VDF = 10; +const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.1'; const MAX_RETRIES = 10; const RETRY_DELAY_MS = 10000; -const VDF_JOB_IMAGE = 'randao/puzzle-gen:v0.1.1'; const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); const ongoingContainers = new Set(); // Track container IDs of running Docker containers @@ -133,7 +131,7 @@ CREATE TABLE time_lock_puzzles ( } -async function triggerVDFJobPod(): Promise { +async function triggerTimePuzzleJobPod(randomCount: number): Promise { if (ENVIRONMENT === 'cloud') { try { console.log("Cloud environment detected. Launching ECS task."); @@ -145,7 +143,7 @@ async function triggerVDFJobPod(): Promise { console.log("Network config:", cachedNetworkConfig); } - const taskArn = await launchVDFTask(ecs, cachedNetworkConfig, RANDOM_PER_VDF); + const taskArn = await launchVDFTask(ecs, cachedNetworkConfig, randomCount); if (taskArn) { ongoingContainers.add(taskArn); console.log(`ECS task started successfully: ${taskArn}`); @@ -164,19 +162,13 @@ async function triggerVDFJobPod(): Promise { return null; } } else { - // Check if we have reached the maximum number of containers - if (ongoingContainers.size >= MAX_OUTSTANDING_VDF_CONTAINERS) { - console.log(`Maximum outstanding puzzle-gen containers (${MAX_OUTSTANDING_VDF_CONTAINERS}) reached. Not starting new container.`); - return null; - } - const containerName = `puzzle-gen_job_${Date.now()}_${Math.floor(Math.random() * 100000)}`; console.log(`Starting Docker container with name: ${containerName}`); try { if (!pulledDockerimage) { - console.log(`Pulling image: ${VDF_JOB_IMAGE}`); + console.log(`Pulling image: ${TIME_PUZZLE_JOB_IMAGE}`); await new Promise((resolve, reject) => { - docker.pull(VDF_JOB_IMAGE, (err: any, stream: NodeJS.ReadableStream) => { + docker.pull(TIME_PUZZLE_JOB_IMAGE, (err: any, stream: NodeJS.ReadableStream) => { if (err) { return reject(err); } @@ -189,8 +181,9 @@ async function triggerVDFJobPod(): Promise { pulledDockerimage = true; } const container = await docker.createContainer({ - Image: VDF_JOB_IMAGE, - Cmd: ['sh', '-c', `python3 main.py ${RANDOM_PER_VDF}`], + Image: TIME_PUZZLE_JOB_IMAGE, + Cmd: ['sh', '-c', `python3 main.py ${randomCount}`], + Env: [ `DATABASE_TYPE=postgresql`, `DATABASE_HOST=${dbConfig.host}`, @@ -339,40 +332,30 @@ async function shutdown() { async function getMoreRandom(currentCount: number) { const entriesNeeded = MINIMUM_ENTRIES - currentCount; - //TODO this math looked wrong in https://discord.com/channels/1209645894039896074/1333434937537204336/1336000074765045853 console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries...`); ongoingRequest = true; - // Calculate how many containers to spawn - const possibleBatchCount = Math.ceil(entriesNeeded / RANDOM_PER_VDF); - const availableSpawns = Math.min( - possibleBatchCount, - MAX_OUTSTANDING_VDF_CONTAINERS - ongoingContainers.size - ); - - if (availableSpawns <= 0) { - console.log("Max outstanding containers reached. Skipping new container launches."); - ongoingRequest = false; - return; + if (ongoingContainers.size > 0) { + console.log("A puzzle-gen container is already running. Skipping new container launch."); + return null; } - console.log(`Spawning up to ${availableSpawns} containers to generate random values.`); + console.log(`Spawning a single container to generate ${entriesNeeded} random values.`); - for (let i = 0; i < availableSpawns; i++) { - try { - const jobId = await triggerVDFJobPod(); - if (jobId) { - console.log(`Job triggered: ${jobId}`); - ongoingContainers.add(jobId); - } - } catch (error) { - console.error('Error triggering job pod:', error); + try { + const jobId = await triggerTimePuzzleJobPod(entriesNeeded); + if (jobId) { + console.log(`Job triggered: ${jobId}`); + ongoingContainers.add(jobId); } + } catch (error) { + console.error('Error triggering job pod:', error); } } + // Function to check and fetch database entries as needed async function checkAndFetchIfNeeded(client: Client) { try { diff --git a/requester/src/app.ts b/requester/src/app.ts index 74feb04..e20b5ff 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -9,7 +9,7 @@ import { RandomClientConfigBuilder, } from "ao-process-clients"; -const RETRY_DELAY_MS = 2000; // 2 seconds +const RETRY_DELAY_MS = 10000; // 10 seconds const PROVIDER_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes const PROVIDER_REQUEST_TIMEOUT = 60 * 1000; // 1 minute const CHANCE_TO_CALL_RANDOM = 1; diff --git a/terraform/ecs.tf b/terraform/ecs.tf index b2c8d5a..ca10463 100644 --- a/terraform/ecs.tf +++ b/terraform/ecs.tf @@ -62,7 +62,7 @@ resource "aws_ecs_task_definition" "orchestrator_service" { container_definitions = jsonencode([ { name = "orchestrator" - image = "randao/orchestrator:v0.4.2" + image = "randao/orchestrator:v0.4.55" environment = [ { name = "ENVIRONMENT" From cd042b7ad4e74de067d2946be49c3080bae42a34 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 31 Mar 2025 12:56:44 -0400 Subject: [PATCH 41/51] V0.4.55 --- README.md | 96 +++++++++----- docker-compose/README.md | 90 ++++++++++--- orchestrator/src/app.ts | 14 +- terraform/README.md | 278 ++++++++++++++++++++------------------- 4 files changed, 291 insertions(+), 187 deletions(-) diff --git a/README.md b/README.md index 17e5b7f..209ef8c 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,30 @@ # Node Provider Setup Guide -This guide will walk you through getting your randomness provider set up and connected to the network so you can start contributing to the protocal and participating in decentralized randomness! +This guide will walk you through getting your randomness provider set up and connected to the network so you can start contributing to the protocol and participating in decentralized randomness! ## Table of Contents 1. [Introduction](#introduction) 2. [Hardware Requirements](#hardware-requirements) -3. [Deployment Options](#deployment-options) +3. [Randomness Generation](#randomness-generation) +4. [Deployment Options](#deployment-options) - [Option 1: AWS Deployment with Terraform](#option-1-aws-deployment-with-terraform) - - [Option 2: Virtual Machine Deployment with Docker Compose](#option-2-virtual-machine-deployment-with-docker-compose) -4. [Graceful Shutdown Policy](#graceful-shutdown-policy) + - [Option 2: Docker Compose Deployment](#option-2-docker-compose-deployment) +5. [Graceful Shutdown Policy](#graceful-shutdown-policy) +6. [Staking](#staking) --- ## Introduction -As a node provider, you are responsible for ensuring 100% uptime. In the event of downtime, it is mandatory to run the graceful shutdown script to prevent being slashed. +As a node provider, you are responsible for ensuring 100% uptime. In the event of necessary downtime, it is mandatory to run the graceful shutdown process to prevent being slashed. -Your provider does 3 main things. -1. It updates the amount of avalible random it has stored on chain. Each random takes a fair biot of compute to create so that it can be compliant to our commit reveal time delay scheme. -2. It detects someone has requested random from you and it provides the input number to your time delay function. This is not the final random number. -3. It detects all parties have submited their input number and then it provides the output number as well as the proof of history checkpoints to the chain to be verified. This output is the random number that will be used on chain. +Your provider performs 3 main functions: +1. It updates the amount of available random it has stored on chain. Each random value requires significant computation to create, ensuring compliance with our commit-reveal time delay scheme. +2. It detects when someone has requested random from you and provides the input number to your time delay function. This is not the final random number. +3. It detects when all parties have submitted their input numbers and then provides the output number along with the proof of history checkpoints to the chain for verification. This output is the random number that will be used on chain. -It will do these three steps as fast as it can inorder to get the complete random on chain as quickly as possible. Faster providers will be incentivised for their speed and slower ones will been penalized. If a provider is too slow for step 2 it will be slashed a small amount. If a provider is too slow for step 3 they will be considered malicious and slashed heavily. +These steps are executed as quickly as possible to get the complete random value on chain promptly. Faster providers will be incentivized for their speed, while slower ones will be penalized. If a provider is too slow for step 2, it will be slashed a small amount. If a provider is too slow for step 3, they will be considered malicious and slashed heavily. -In the event you need to take your provider offline you must run the gracefull shutdown which will run step 1 once with a value of -1 indicating you are no longer offering random. After that your node will finish all requests in step 2 and 3 then turn off. +If you need to take your provider offline, you must run the graceful shutdown process, which will execute step 1 once with a value of -1, indicating you are no longer offering random. After that, your node will finish all pending requests in steps 2 and 3 before shutting down. --- @@ -32,52 +34,86 @@ To run a node, the following hardware specifications are required: - **Minimum Hardware Requirements:** - 4 GB memory - 2 CPU cores -- **Recommended Deployment:** Access to an AWS account (we handle the configuration) - **Note:** These requirements will increase over time to meet network demands. --- +## Randomness Generation -## What the hardware runs -The hardware you stand up runs 3 services. It stands up a provider. A database for the provider and it spins up temporary jobs to generate random and store it in the database. -In AWS the cheapest and highest performance solution is used for each of these. when running with docekr compose your machinbe will run each of these services itself in a containerized environment. Quick and scalable but not as cheap or efficient as the AWS solution. +Our system now uses cryptographic time lock puzzles instead of Verifiable Delay Functions (VDF) for randomness generation. The provider "mines" for these puzzles, which creates a provable time delay between commitment and revelation of random values. This approach enhances security while maintaining verifiability of the randomness generated. +The time lock puzzles require significant initial computation but become less resource-intensive once you've mined and stored a sufficient number of them for sale. This makes the provider more efficient over time as your puzzle inventory grows. + +--- + +## What the Hardware Runs +The hardware you set up runs 3 key services: +1. A provider service +2. A database for the provider +3. Temporary jobs to generate random values and store them in the database + +Both of our deployment options are designed to run these services efficiently. + +--- ## Deployment Options +We now fully support and recommend two deployment methods, depending on your infrastructure preferences and capabilities: + ### Option 1: AWS Deployment with Terraform -This is the recommended method, providing the best performance and uptime at the lowest cost. +This method leverages AWS services for optimal performance, scalability, and cost-effectiveness. -[TerraForm setup](./terraform/README.md) +**Advantages:** +- Most cost-effective solution at scale +- Guaranteed 100% uptime with AWS reliability +- Optimized for performance with managed services +- Automatic scaling based on demand +While this option may be more technically complex to set up initially, it provides the best long-term solution for dedicated providers. -### Option 2: Virtual Machine Deployment with Docker Compose -This option may cost more and depends on the uptime of the hardware you use. It is not recommended for long-term or high-performance node operators. +[Terraform setup guide](./terraform/README.md) -[Docker-compose setup](./docker-compose/README.md) +### Option 2: Docker Compose Deployment +This method allows you to run the provider on your own hardware using Docker containers. +**Advantages:** +- Easier to set up if you have spare hardware available +- More straightforward for users familiar with Docker +- Direct control over your infrastructure +- Simpler technical requirements -## Graceful Shutdown Policy -To avoid being slashed, it is critical to run the graceful shutdown in the event of downtime. Failing to do so may result in penalties. +This option is less resource-intensive once you've mined enough time lock puzzles and stored them for sale. -To run this go to ar://randao and navigate to your node and select the "SHUT DOWN" button and sign the transaction. -This will tell your provider to stop serving random. +[Docker Compose setup guide](./docker-compose/README.md) -After the maintinance is done and your provider is back up again click "START UP" button. -This will tell your provider to start serving random. +Both methods are fully supported and recommended as long as you can ensure 100% uptime or follow the graceful shutdown procedure during maintenance periods. --- +## Graceful Shutdown Policy +To avoid being slashed, it is critical to run the graceful shutdown in the event of planned downtime. Failing to do so may result in penalties. + +To run a graceful shutdown: +1. Go to ar://randao +2. Navigate to your node +3. Select the "SHUT DOWN" button and sign the transaction + +This will tell your provider to stop serving random values. + +After maintenance is complete and your provider is back online: +1. Click the "START UP" button +2. This will signal your provider to resume serving random values + +--- ## Staking -After successfully setting up your node, you will need to provide proof that the gateway is operational. +After successfully setting up your node, you will need to provide proof that the gateway is operational: 1. Show logs from the node setup to Ethan for verification. - 2. Upon confirmation, Ethan will provide your provider address with the necessary funds. - 3. Navigate to ar://randao to stake your funds and configure your provider information. By completing this process, you will fully activate your node and ensure it is ready for network participation. -Thank you for contributing to the network's success! \ No newline at end of file + +Thank you for contributing to the network's success! diff --git a/docker-compose/README.md b/docker-compose/README.md index 28bf61c..3600fd8 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -1,33 +1,81 @@ -# Steps to Deploy: -1. **Install Docker Compose:** - Follow the [official Docker Compose installation guide](https://docs.docker.com/compose/install/). +# Docker Compose Setup Guide +This guide walks you through deploying a randomness provider using Docker Compose on your own hardware. -2. **Deploy Node:** - Navigate to the Docker Compose directory and run: +## Prerequisites + +- A machine meeting the minimum hardware requirements (4 GB memory, 2 CPU cores) +- Reliable internet connection +- Ability to ensure 100% uptime or follow graceful shutdown procedures + +## Steps to Deploy + +1. **Install Docker and Docker Compose** + Follow the [official Docker Compose installation guide](https://docs.docker.com/compose/install/) for your operating system. + +2. **Configure Environment Variables** + Navigate to the Docker Compose directory and create your environment file: ```bash cp .env.example .env ``` -Then fill in all of the variables: -Required variables: -- `provider_id`: Your unique provider identifier -- `local_db_user`: Database username -- `local_db_password`: Database password -- `local_wallet_json`: Wallet JSON (either direct or via file) - -Optional variables (with defaults): -- `aws_region`: AWS region (default: us-east-1) -- `db_name`: Database name (default: orchestrator_db) -- `secrets_prefix`: Prefix for secrets (default: /orchestrator) - -3. **Deploy Node:** - Navigate to the Docker Compose directory and run: + + Then edit the `.env` file and fill in all required variables: + + **Required Variables:** + - `provider_id`: Your unique provider identifier + - `local_db_user`: Database username + - `local_db_password`: Database password + - `local_wallet_json`: Wallet JSON (either direct or via file path) + + **Optional Variables (with defaults):** + - `aws_region`: AWS region (default: us-east-1) + - `db_name`: Database name (default: orchestrator_db) + - `secrets_prefix`: Prefix for secrets (default: /orchestrator) + +3. **Deploy Your Provider** + From the Docker Compose directory, run: ```bash docker-compose up -d ``` -This setup will work but may not guarantee the same performance or reliability as the AWS-based deployment. +4. **Verify Deployment** + Check the status of your containers: + ```bash + docker-compose ps + ``` + + View logs to ensure everything is running correctly: + ```bash + docker-compose logs -f + ``` + +## How It Works + +This deployment creates three containerized services: + +1. **Provider Service**: Handles the main provider functionality and communicates with the blockchain +2. **Database**: Stores cryptographic time lock puzzles and provider state +3. **Puzzle Generator**: Creates time lock puzzles through the "mining" process + +## Advantages of Docker Compose + +- **Easier Setup**: More straightforward for those with existing hardware +- **Direct Control**: Full control over your infrastructure +- **Simplified Management**: Easy to manage with standard Docker commands +- **Lower Technical Barrier**: Simpler for those familiar with containerization + +## Maintenance + +Remember to follow the graceful shutdown procedure in the main documentation when performing maintenance on your Docker-based provider. Never kill the containers without proper shutdown or you risk being slashed. + +To update your provider when new versions are released: + +```bash +docker-compose pull +docker-compose down +docker-compose up -d +``` --- -[Main docs](../README.md) \ No newline at end of file +[Return to Main Documentation](../README.md) diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 77f3ad5..6b9b1a1 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -13,18 +13,28 @@ const AO_CONFIG = { }; let randomClientInstance: RandomClient | null = null; +let lastInitTime: number = 0; +const REINIT_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds async function getRandomClient(): Promise { - - if (!randomClientInstance) { + const currentTime = Date.now(); + + if (!randomClientInstance || (currentTime - lastInitTime) > REINIT_INTERVAL) { randomClientInstance = ((await RandomClient.defaultBuilder()) .withAOConfig(AO_CONFIG)) .withWallet(JSON.parse(process.env.WALLET_JSON!)) .build(); + lastInitTime = currentTime; } + return randomClientInstance; } +// Optional: Auto-reinitialize on a timer +setInterval(() => { + randomClientInstance = null; +}, REINIT_INTERVAL); + const docker = new Docker(); // Constants for configuration diff --git a/terraform/README.md b/terraform/README.md index 45e7540..931ab97 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -1,107 +1,117 @@ -#### Steps to Deploy: -1. **Install Terraform:** +# AWS Terraform Setup Guide + +This guide walks you through deploying a randomness provider using Terraform and AWS cloud services for optimal performance and reliability. + +## Prerequisites + +- An AWS account with appropriate permissions +- Basic familiarity with AWS services and Terraform +- Terraform installed on your local machine + +## Steps to Deploy + +1. **Install Terraform** Follow the [official Terraform installation guide](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli). -2. **Log into AWS and set up IAM user and Policy** - Go To IAM and create a new policy called RandAO-Provider-Admin - Paste this in the JSON - ```{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ecs:CreateCluster", - "ecs:DeleteCluster", - "ecs:CreateService", - "ecs:DeleteService", - "ecs:UpdateService", - "ecs:RegisterTaskDefinition", - "ecs:DeregisterTaskDefinition", - "ecs:ListTaskDefinitions", - "ecs:DescribeTaskDefinition", - "ecs:PutClusterCapacityProviders", - "ecs:DescribeClusters" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:CreateRole", - "iam:DeleteRole", - "iam:GetRole", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy", - "iam:AttachRolePolicy", - "iam:DetachRolePolicy", - "iam:PassRole" - ], - "Resource": "arn:aws:iam::*:role/orchestrator-*" - }, - { - "Effect": "Allow", - "Action": [ - "secretsmanager:CreateSecret", - "secretsmanager:DeleteSecret", - "secretsmanager:GetSecretValue", - "secretsmanager:PutSecretValue", - "secretsmanager:UpdateSecret", - "secretsmanager:TagResource" - ], - "Resource": "arn:aws:secretsmanager:*:*:secret:/orchestrator/*" - }, - { - "Effect": "Allow", - "Action": [ - "rds:CreateDBInstance", - "rds:DeleteDBInstance", - "rds:ModifyDBInstance", - "rds:DescribeDBInstances", - "rds:CreateDBSubnetGroup", - "rds:DeleteDBSubnetGroup", - "rds:ModifyDBSubnetGroup", - "rds:AddTagsToResource" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:DeleteLogGroup", - "logs:PutRetentionPolicy" - ], - "Resource": "arn:aws:logs:*:*:log-group:*" - }, - { - "Effect": "Allow", - "Action": [ - "ec2:CreateSecurityGroup", - "ec2:DeleteSecurityGroup", - "ec2:AuthorizeSecurityGroupIngress", - "ec2:RevokeSecurityGroupIngress", - "ec2:CreateVpcEndpoint", - "ec2:DeleteVpcEndpoints", - "ec2:DescribeVpcEndpoints", - "ec2:DescribeSecurityGroups", - "ec2:DescribeNetworkInterfaces", - "ec2:CreateTags" - ], - "Resource": "*" - } - ] -} -``` - Save the JSON and name it - Click on Users and create a new user called TeraformDeployer - Attach this new policy directly - Save the User - Click on the user and go to the Security Credentials tab - Create an access key and choose CLI - Save the variables for the next step - -3. **Configure AWS Environment Variables:** +2. **Set Up IAM User and Policy** + 1. Go to AWS IAM and create a new policy called `RandAO-Provider-Admin` + 2. Select the JSON tab and paste this policy: + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecs:CreateCluster", + "ecs:DeleteCluster", + "ecs:CreateService", + "ecs:DeleteService", + "ecs:UpdateService", + "ecs:RegisterTaskDefinition", + "ecs:DeregisterTaskDefinition", + "ecs:ListTaskDefinitions", + "ecs:DescribeTaskDefinition", + "ecs:PutClusterCapacityProviders", + "ecs:DescribeClusters" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iam:CreateRole", + "iam:DeleteRole", + "iam:GetRole", + "iam:PutRolePolicy", + "iam:DeleteRolePolicy", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy", + "iam:PassRole" + ], + "Resource": "arn:aws:iam::*:role/orchestrator-*" + }, + { + "Effect": "Allow", + "Action": [ + "secretsmanager:CreateSecret", + "secretsmanager:DeleteSecret", + "secretsmanager:GetSecretValue", + "secretsmanager:PutSecretValue", + "secretsmanager:UpdateSecret", + "secretsmanager:TagResource" + ], + "Resource": "arn:aws:secretsmanager:*:*:secret:/orchestrator/*" + }, + { + "Effect": "Allow", + "Action": [ + "rds:CreateDBInstance", + "rds:DeleteDBInstance", + "rds:ModifyDBInstance", + "rds:DescribeDBInstances", + "rds:CreateDBSubnetGroup", + "rds:DeleteDBSubnetGroup", + "rds:ModifyDBSubnetGroup", + "rds:AddTagsToResource" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:DeleteLogGroup", + "logs:PutRetentionPolicy" + ], + "Resource": "arn:aws:logs:*:*:log-group:*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateSecurityGroup", + "ec2:DeleteSecurityGroup", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:CreateVpcEndpoint", + "ec2:DeleteVpcEndpoints", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeSecurityGroups", + "ec2:DescribeNetworkInterfaces", + "ec2:CreateTags" + ], + "Resource": "*" + } + ] + } + ``` + 3. Save the policy and name it + 4. Go to Users and create a new user called `TerraformDeployer` + 5. Attach the `RandAO-Provider-Admin` policy directly + 6. Create an access key for the user (choose CLI) + 7. Save the access key ID and secret access key for the next step + +3. **Configure AWS Environment Variables** Open a terminal and enter the following commands: ```bash export AWS_ACCESS_KEY_ID="your-access-key-id" @@ -109,30 +119,27 @@ export AWS_REGION="your-region" # e.g., us-east-1 ``` - -4. **Set up ENV variables:** +4. **Set Up Terraform Variables** Navigate to the Terraform directory of the project: ```bash cp terraform.tfvars.example terraform.tfvars ``` - Fill in all of the variables with your info. -## Variables Reference + Edit the `terraform.tfvars` file and fill in all required variables: -Required variables: -- `provider_id`: Your unique provider identifier -- `local_db_user`: Database username -- `local_db_password`: Database password -- `local_wallet_json`: Wallet JSON (either direct or via file) + **Required Variables:** + - `provider_id`: Your unique provider identifier + - `local_db_user`: Database username + - `local_db_password`: Database password + - `local_wallet_json`: Wallet JSON (either direct or via file path) -Optional variables (with defaults): -- `aws_region`: AWS region (default: us-east-1) -- `db_name`: Database name (default: orchestrator_db) -- `secrets_prefix`: Prefix for secrets (default: /orchestrator) + **Optional Variables (with defaults):** + - `aws_region`: AWS region (default: us-east-1) + - `db_name`: Database name (default: orchestrator_db) + - `secrets_prefix`: Prefix for secrets (default: /orchestrator) - The Database configuration is highly suggested and the secrets can be left alone as its just the name of the secrets + The database configuration is highly suggested, and the secrets can be left at default values as they're just the names of the secrets. - -5. **Initialize and Apply Terraform Configuration:** +5. **Initialize and Apply Terraform Configuration** Navigate to the Terraform directory of the project and run: ```bash terraform init @@ -140,19 +147,26 @@ Optional variables (with defaults): ``` Type `yes` when prompted to confirm. -This setup ensures your node is deployed with the highest uptime and optimal performance. +6. **Verify Deployment** + Open the AWS console and navigate to the ECS console to view your running services. -Please open up the AWS console and show the logs of this to Ethan top receive the Tokens to stake +## How It Works +This deployment creates the following AWS resources: -# Terraform Configuration for Randomness Provider +1. **ECS Cluster**: Runs the orchestrator service and puzzle generator jobs +2. **RDS**: PostgreSQL database for storing time lock puzzles and provider state +3. **Secrets Manager**: Securely stores database credentials and wallet information +4. **IAM**: Roles and policies for ECS tasks and secrets access +5. **CloudWatch**: Log groups for monitoring your provider -This Terraform configuration sets up: -- One orchestrator service on ECS Fargate -- One PostgreSQL RDS instance -- VDF Fargate spot job configuration -- AWS Secrets Manager for sensitive data +## Advantages of AWS Deployment +- **Cost Efficiency at Scale**: Most cost-effective for dedicated providers +- **Maximum Reliability**: AWS services offer SLAs for high availability +- **Automatic Scaling**: Resources scale based on demand +- **Managed Services**: AWS handles infrastructure maintenance +- **High Performance**: Optimized for speed and reliability ## Security Notes @@ -161,14 +175,10 @@ This Terraform configuration sets up: - In production, sensitive values are stored in AWS Secrets Manager - Local development variables are only used for initial setup and testing -## Infrastructure Components - -- **ECS Cluster**: Runs the orchestrator service and VDF jobs -- **RDS**: PostgreSQL database for the orchestrator -- **Secrets Manager**: Securely stores database credentials and wallet -- **IAM**: Roles and policies for ECS tasks and secrets access -- **CloudWatch**: Log groups for monitoring +## Staking Process +After deployment, please open the AWS console and share the logs with Ethan to receive the tokens needed for staking. Follow the main documentation for the staking process. +--- -[Main docs](../README.md) \ No newline at end of file +[Return to Main Documentation](../README.md) From f5d65a4e5db63669f7b2ee6c62226eb57b615f5c Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 1 Apr 2025 20:26:24 -0400 Subject: [PATCH 42/51] added docs --- docker-compose/README.md | 47 ++++++++++++++++++++++ docker-compose/mass-dev-docker-compose.yml | 2 +- orchestrator/src/app.ts | 5 ++- requester/docs/development.md | 2 +- requester/src/app.ts | 2 +- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/docker-compose/README.md b/docker-compose/README.md index 3600fd8..fa60c4a 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -64,6 +64,53 @@ This deployment creates three containerized services: - **Simplified Management**: Easy to manage with standard Docker commands - **Lower Technical Barrier**: Simpler for those familiar with containerization +## Troubleshooting + +If you encounter issues with your provider, here are some common problems and solutions: + +### "Provider Not Found" Error +- Ensure your provider is properly staked at https://providers_randao.ar.io +- Wait for blockchain confirmation as it may take some time for your stake to be recognized +- Check your wallet configuration in the `.env` file + +### Network Connectivity Issues +- If your provider can't connect to the network, it may be due to network congestion +- Wait for network conditions to improve before attempting to restart +- Check your internet connection and firewall settings + +### Slow or Unresponsive Provider +- Check system resources to ensure your host has sufficient CPU and memory +- Monitor the logs for any error messages or warnings: + ```bash + docker-compose logs -f + ``` +- If the puzzle generator is struggling, consider scaling up your hardware + +### General Issues +- Try restarting the containers: + ```bash + docker-compose restart + ``` +- For more persistent issues, you can try a full reset: + ```bash + docker-compose down + docker-compose up -d + ``` +- Ensure your container has the latest version: + ```bash + docker-compose pull + docker-compose down + docker-compose up -d + ``` + +### Database Issues +- If the database container fails to start, check logs for specific errors: + ```bash + docker-compose logs db + ``` +- Ensure the database password in your `.env` file doesn't contain special characters that need escaping +- Verify database volume permissions if running on Linux + ## Maintenance Remember to follow the graceful shutdown procedure in the main documentation when performing maintenance on your Docker-based provider. Never kill the containers without proper shutdown or you risk being slashed. diff --git a/docker-compose/mass-dev-docker-compose.yml b/docker-compose/mass-dev-docker-compose.yml index f2c0528..fc88394 100644 --- a/docker-compose/mass-dev-docker-compose.yml +++ b/docker-compose/mass-dev-docker-compose.yml @@ -599,7 +599,7 @@ services: - dbeaver-data:/opt/cloudbeaver/workspace requester: - image: randao/requester:v0.4.2 + image: randao/requester:v0.4.5 environment: REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 6b9b1a1..91875a3 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -38,7 +38,8 @@ setInterval(() => { const docker = new Docker(); // Constants for configuration -const POLLING_INTERVAL_MS = 10000; +const POLLING_INTERVAL_MS = 30000; //30 seconds +const DATABASE_CHECK_TIME = 60000; //60 seconds const MINIMUM_ENTRIES = 1000; const DRYRUNTIMEOUT = 30000; // 30 seconds const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.1'; @@ -881,7 +882,7 @@ async function run(): Promise { console.error("Error in checkAndFetchIfNeeded:", error); }); - }, 10000); + }, DATABASE_CHECK_TIME); setInterval(async () => { await monitorDockerContainers(); diff --git a/requester/docs/development.md b/requester/docs/development.md index a275bc2..412c1fb 100644 --- a/requester/docs/development.md +++ b/requester/docs/development.md @@ -2,4 +2,4 @@ To build: Save all files Run: -docker build -t randao/requester:latest -t randao/requester:v0.4.0 . \ No newline at end of file +docker build -t randao/requester:latest -t randao/requester:v0.4.5 . \ No newline at end of file diff --git a/requester/src/app.ts b/requester/src/app.ts index e20b5ff..c79b655 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -9,7 +9,7 @@ import { RandomClientConfigBuilder, } from "ao-process-clients"; -const RETRY_DELAY_MS = 10000; // 10 seconds +const RETRY_DELAY_MS = 30000; // 30 seconds const PROVIDER_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes const PROVIDER_REQUEST_TIMEOUT = 60 * 1000; // 1 minute const CHANCE_TO_CALL_RANDOM = 1; From 26e206312d350d4a5c7b03ff34423b2dee506a2e Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 4 Apr 2025 10:10:29 -0400 Subject: [PATCH 43/51] cleaned up --- docker-compose/dev-docker-compose.yml | 154 ----- docker-compose/docker-compose.yml | 1 - docker-compose/mass-dev-docker-compose.yml | 643 --------------------- requester/src/app.ts | 2 +- 4 files changed, 1 insertion(+), 799 deletions(-) delete mode 100644 docker-compose/dev-docker-compose.yml delete mode 100644 docker-compose/mass-dev-docker-compose.yml diff --git a/docker-compose/dev-docker-compose.yml b/docker-compose/dev-docker-compose.yml deleted file mode 100644 index 5b5d1ad..0000000 --- a/docker-compose/dev-docker-compose.yml +++ /dev/null @@ -1,154 +0,0 @@ -# version: '3.8' - -services: - # Instance 1 - postgres1: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_1:-myuser1} - POSTGRES_PASSWORD: ${DB_PASSWORD_1:-mypassword1} - POSTGRES_DB: ${DB_NAME_1:-mydatabase1} - ports: - - "5432:5432" - networks: - - backend - volumes: - - pgdata1:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_1:-myuser1} -d ${DB_NAME_1:-mydatabase1}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator1: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres1: - condition: service_healthy - environment: - DB_HOST: postgres1 - DB_PORT: 5432 - DB_USER: ${DB_USER_1:-myuser1} - DB_PASSWORD: ${DB_PASSWORD_1:-mypassword1} - DB_NAME: ${DB_NAME_1:-mydatabase1} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_1} - PROVIDER_ID: ${PROVIDER_ID_1} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 2 - postgres2: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_2:-myuser2} - POSTGRES_PASSWORD: ${DB_PASSWORD_2:-mypassword2} - POSTGRES_DB: ${DB_NAME_2:-mydatabase2} - ports: - - "5433:5432" - networks: - - backend - volumes: - - pgdata2:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_2:-myuser2} -d ${DB_NAME_2:-mydatabase2}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator2: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres2: - condition: service_healthy - environment: - DB_HOST: postgres2 - DB_PORT: 5432 - DB_USER: ${DB_USER_2:-myuser2} - DB_PASSWORD: ${DB_PASSWORD_2:-mypassword2} - DB_NAME: ${DB_NAME_2:-mydatabase2} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_2} - PROVIDER_ID: ${PROVIDER_ID_2} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 3 - postgres3: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_3:-myuser3} - POSTGRES_PASSWORD: ${DB_PASSWORD_3:-mypassword3} - POSTGRES_DB: ${DB_NAME_3:-mydatabase3} - ports: - - "5434:5432" - networks: - - backend - volumes: - - pgdata3:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_3:-myuser3} -d ${DB_NAME_3:-mydatabase3}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator3: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres3: - condition: service_healthy - environment: - DB_HOST: postgres3 - DB_PORT: 5432 - DB_USER: ${DB_USER_3:-myuser3} - DB_PASSWORD: ${DB_PASSWORD_3:-mypassword3} - DB_NAME: ${DB_NAME_3:-mydatabase3} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_3} - PROVIDER_ID: ${PROVIDER_ID_3} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Single DBeaver Instance - dbeaver: - image: dbeaver/cloudbeaver:23.2.0 - environment: - CB_SERVER_SERVER_PORT: 8080 - CB_SERVER_ADMIN_NAME: "admin" - CB_SERVER_ADMIN_PASSWORD: "admin123" - networks: - - backend - ports: - - "8080:8978" - volumes: - - dbeaver-data:/opt/cloudbeaver/workspace - - requester: - image: randao/requester:v0.2.8 - environment: - REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} - - -networks: - backend: - name: backend - driver: bridge - -volumes: - pgdata1: - driver: local - pgdata2: - driver: local - pgdata3: - driver: local - dbeaver-data: - driver: local diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 120c8a8..4fff7ae 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -31,7 +31,6 @@ services: ENVIRONMENT: local PATH_TO_WALLET: /app/wallet.json # Path inside the container WALLET_JSON: ${WALLET_JSON} - PROVIDER_ID: ${PROVIDER_ID} DOCKER_NETWORK: backend # Passing the network name networks: - backend diff --git a/docker-compose/mass-dev-docker-compose.yml b/docker-compose/mass-dev-docker-compose.yml deleted file mode 100644 index fc88394..0000000 --- a/docker-compose/mass-dev-docker-compose.yml +++ /dev/null @@ -1,643 +0,0 @@ -services: - # Instance 1 - postgres1: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_1:-myuser1} - POSTGRES_PASSWORD: ${DB_PASSWORD_1:-mypassword1} - POSTGRES_DB: ${DB_NAME_1:-mydatabase1} - ports: - - "5432:5432" - networks: - - backend - volumes: - - pgdata1:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_1:-myuser1} -d ${DB_NAME_1:-mydatabase1}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator1: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres1: - condition: service_healthy - environment: - DB_HOST: postgres1 - DB_PORT: 5432 - DB_USER: ${DB_USER_1:-myuser1} - DB_PASSWORD: ${DB_PASSWORD_1:-mypassword1} - DB_NAME: ${DB_NAME_1:-mydatabase1} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_1} - PROVIDER_ID: ${PROVIDER_ID_1} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 2 - postgres2: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_2:-myuser2} - POSTGRES_PASSWORD: ${DB_PASSWORD_2:-mypassword2} - POSTGRES_DB: ${DB_NAME_2:-mydatabase2} - ports: - - "5433:5432" - networks: - - backend - volumes: - - pgdata2:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_2:-myuser2} -d ${DB_NAME_2:-mydatabase2}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator2: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres2: - condition: service_healthy - environment: - DB_HOST: postgres2 - DB_PORT: 5432 - DB_USER: ${DB_USER_2:-myuser2} - DB_PASSWORD: ${DB_PASSWORD_2:-mypassword2} - DB_NAME: ${DB_NAME_2:-mydatabase2} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_2} - PROVIDER_ID: ${PROVIDER_ID_2} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 3 - postgres3: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_3:-myuser3} - POSTGRES_PASSWORD: ${DB_PASSWORD_3:-mypassword3} - POSTGRES_DB: ${DB_NAME_3:-mydatabase3} - ports: - - "5434:5432" - networks: - - backend - volumes: - - pgdata3:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_3:-myuser3} -d ${DB_NAME_3:-mydatabase3}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator3: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres3: - condition: service_healthy - environment: - DB_HOST: postgres3 - DB_PORT: 5432 - DB_USER: ${DB_USER_3:-myuser3} - DB_PASSWORD: ${DB_PASSWORD_3:-mypassword3} - DB_NAME: ${DB_NAME_3:-mydatabase3} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_3} - PROVIDER_ID: ${PROVIDER_ID_3} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 4 - postgres4: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_4:-myuser4} - POSTGRES_PASSWORD: ${DB_PASSWORD_4:-mypassword4} - POSTGRES_DB: ${DB_NAME_4:-mydatabase4} - ports: - - "5435:5432" - networks: - - backend - volumes: - - pgdata4:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_4:-myuser4} -d ${DB_NAME_4:-mydatabase4}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator4: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres4: - condition: service_healthy - environment: - DB_HOST: postgres4 - DB_PORT: 5432 - DB_USER: ${DB_USER_4:-myuser4} - DB_PASSWORD: ${DB_PASSWORD_4:-mypassword4} - DB_NAME: ${DB_NAME_4:-mydatabase4} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_4} - PROVIDER_ID: ${PROVIDER_ID_4} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 5 - postgres5: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_5:-myuser5} - POSTGRES_PASSWORD: ${DB_PASSWORD_5:-mypassword5} - POSTGRES_DB: ${DB_NAME_5:-mydatabase5} - ports: - - "5436:5432" - networks: - - backend - volumes: - - pgdata5:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_5:-myuser5} -d ${DB_NAME_5:-mydatabase5}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator5: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres5: - condition: service_healthy - environment: - DB_HOST: postgres5 - DB_PORT: 5432 - DB_USER: ${DB_USER_5:-myuser5} - DB_PASSWORD: ${DB_PASSWORD_5:-mypassword5} - DB_NAME: ${DB_NAME_5:-mydatabase5} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_5} - PROVIDER_ID: ${PROVIDER_ID_5} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 6 - postgres6: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_6:-myuser6} - POSTGRES_PASSWORD: ${DB_PASSWORD_6:-mypassword6} - POSTGRES_DB: ${DB_NAME_6:-mydatabase6} - ports: - - "5437:5432" - networks: - - backend - volumes: - - pgdata6:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_6:-myuser6} -d ${DB_NAME_6:-mydatabase6}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator6: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres6: - condition: service_healthy - environment: - DB_HOST: postgres6 - DB_PORT: 5432 - DB_USER: ${DB_USER_6:-myuser6} - DB_PASSWORD: ${DB_PASSWORD_6:-mypassword6} - DB_NAME: ${DB_NAME_6:-mydatabase6} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_6} - PROVIDER_ID: ${PROVIDER_ID_6} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 7 - postgres7: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_7:-myuser7} - POSTGRES_PASSWORD: ${DB_PASSWORD_7:-mypassword7} - POSTGRES_DB: ${DB_NAME_7:-mydatabase7} - ports: - - "5438:5432" - networks: - - backend - volumes: - - pgdata7:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_7:-myuser7} -d ${DB_NAME_7:-mydatabase7}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator7: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres7: - condition: service_healthy - environment: - DB_HOST: postgres7 - DB_PORT: 5432 - DB_USER: ${DB_USER_7:-myuser7} - DB_PASSWORD: ${DB_PASSWORD_7:-mypassword7} - DB_NAME: ${DB_NAME_7:-mydatabase7} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_7} - PROVIDER_ID: ${PROVIDER_ID_7} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 8 - postgres8: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_8:-myuser8} - POSTGRES_PASSWORD: ${DB_PASSWORD_8:-mypassword8} - POSTGRES_DB: ${DB_NAME_8:-mydatabase8} - ports: - - "5439:5432" - networks: - - backend - volumes: - - pgdata8:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_8:-myuser8} -d ${DB_NAME_8:-mydatabase8}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator8: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres8: - condition: service_healthy - environment: - DB_HOST: postgres8 - DB_PORT: 5432 - DB_USER: ${DB_USER_8:-myuser8} - DB_PASSWORD: ${DB_PASSWORD_8:-mypassword8} - DB_NAME: ${DB_NAME_8:-mydatabase8} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_8} - PROVIDER_ID: ${PROVIDER_ID_8} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 9 - postgres9: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_9:-myuser9} - POSTGRES_PASSWORD: ${DB_PASSWORD_9:-mypassword9} - POSTGRES_DB: ${DB_NAME_9:-mydatabase9} - ports: - - "5440:5432" - networks: - - backend - volumes: - - pgdata9:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_9:-myuser9} -d ${DB_NAME_9:-mydatabase9}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator9: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres9: - condition: service_healthy - environment: - DB_HOST: postgres9 - DB_PORT: 5432 - DB_USER: ${DB_USER_9:-myuser9} - DB_PASSWORD: ${DB_PASSWORD_9:-mypassword9} - DB_NAME: ${DB_NAME_9:-mydatabase9} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_9} - PROVIDER_ID: ${PROVIDER_ID_9} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 10 - postgres10: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_10:-myuser10} - POSTGRES_PASSWORD: ${DB_PASSWORD_10:-mypassword10} - POSTGRES_DB: ${DB_NAME_10:-mydatabase10} - ports: - - "5441:5432" - networks: - - backend - volumes: - - pgdata10:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_10:-myuser10} -d ${DB_NAME_10:-mydatabase10}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator10: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres10: - condition: service_healthy - environment: - DB_HOST: postgres10 - DB_PORT: 5432 - DB_USER: ${DB_USER_10:-myuser10} - DB_PASSWORD: ${DB_PASSWORD_10:-mypassword10} - DB_NAME: ${DB_NAME_10:-mydatabase10} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_10} - PROVIDER_ID: ${PROVIDER_ID_10} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 11 - postgres11: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_11:-myuser11} - POSTGRES_PASSWORD: ${DB_PASSWORD_11:-mypassword11} - POSTGRES_DB: ${DB_NAME_11:-mydatabase11} - ports: - - "5442:5432" - networks: - - backend - volumes: - - pgdata11:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_11:-myuser11} -d ${DB_NAME_11:-mydatabase11}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator11: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres11: - condition: service_healthy - environment: - DB_HOST: postgres11 - DB_PORT: 5432 - DB_USER: ${DB_USER_11:-myuser11} - DB_PASSWORD: ${DB_PASSWORD_11:-mypassword11} - DB_NAME: ${DB_NAME_11:-mydatabase11} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_11} - PROVIDER_ID: ${PROVIDER_ID_11} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 12 - postgres12: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_12:-myuser12} - POSTGRES_PASSWORD: ${DB_PASSWORD_12:-mypassword12} - POSTGRES_DB: ${DB_NAME_12:-mydatabase12} - ports: - - "5443:5432" - networks: - - backend - volumes: - - pgdata12:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_12:-myuser12} -d ${DB_NAME_12:-mydatabase12}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator12: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres12: - condition: service_healthy - environment: - DB_HOST: postgres12 - DB_PORT: 5432 - DB_USER: ${DB_USER_12:-myuser12} - DB_PASSWORD: ${DB_PASSWORD_12:-mypassword12} - DB_NAME: ${DB_NAME_12:-mydatabase12} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_12} - PROVIDER_ID: ${PROVIDER_ID_12} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 13 - postgres13: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_13:-myuser13} - POSTGRES_PASSWORD: ${DB_PASSWORD_13:-mypassword13} - POSTGRES_DB: ${DB_NAME_13:-mydatabase13} - ports: - - "5444:5432" - networks: - - backend - volumes: - - pgdata13:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_13:-myuser13} -d ${DB_NAME_13:-mydatabase13}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator13: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres13: - condition: service_healthy - environment: - DB_HOST: postgres13 - DB_PORT: 5432 - DB_USER: ${DB_USER_13:-myuser13} - DB_PASSWORD: ${DB_PASSWORD_13:-mypassword13} - DB_NAME: ${DB_NAME_13:-mydatabase13} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_13} - PROVIDER_ID: ${PROVIDER_ID_13} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 14 - postgres14: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_14:-myuser14} - POSTGRES_PASSWORD: ${DB_PASSWORD_14:-mypassword14} - POSTGRES_DB: ${DB_NAME_14:-mydatabase14} - ports: - - "5445:5432" - networks: - - backend - volumes: - - pgdata14:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_14:-myuser14} -d ${DB_NAME_14:-mydatabase14}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator14: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres14: - condition: service_healthy - environment: - DB_HOST: postgres14 - DB_PORT: 5432 - DB_USER: ${DB_USER_14:-myuser14} - DB_PASSWORD: ${DB_PASSWORD_14:-mypassword14} - DB_NAME: ${DB_NAME_14:-mydatabase14} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_14} - PROVIDER_ID: ${PROVIDER_ID_14} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Instance 15 - postgres15: - image: postgres:13 - environment: - POSTGRES_USER: ${DB_USER_15:-myuser15} - POSTGRES_PASSWORD: ${DB_PASSWORD_15:-mypassword15} - POSTGRES_DB: ${DB_NAME_15:-mydatabase15} - ports: - - "5446:5432" - networks: - - backend - volumes: - - pgdata15:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER_15:-myuser15} -d ${DB_NAME_15:-mydatabase15}"] - interval: 10s - timeout: 5s - retries: 5 - - orchestrator15: - image: randao/orchestrator:v0.4.55 - depends_on: - postgres15: - condition: service_healthy - environment: - DB_HOST: postgres15 - DB_PORT: 5432 - DB_USER: ${DB_USER_15:-myuser15} - DB_PASSWORD: ${DB_PASSWORD_15:-mypassword15} - DB_NAME: ${DB_NAME_15:-mydatabase15} - ENVIRONMENT: local - WALLET_JSON: ${WALLET_JSON_15} - PROVIDER_ID: ${PROVIDER_ID_15} - DOCKER_NETWORK: backend - networks: - - backend - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - # Single DBeaver Instance - dbeaver: - image: dbeaver/cloudbeaver:23.2.0 - environment: - CB_SERVER_SERVER_PORT: 8080 - CB_SERVER_ADMIN_NAME: "admin" - CB_SERVER_ADMIN_PASSWORD: "admin123" - networks: - - backend - ports: - - "8080:8978" - volumes: - - dbeaver-data:/opt/cloudbeaver/workspace - - requester: - image: randao/requester:v0.4.5 - environment: - REQUEST_WALLET_JSON: ${REQUEST_WALLET_JSON} - -networks: - backend: - name: backend - driver: bridge - -volumes: - pgdata1: - driver: local - pgdata2: - driver: local - pgdata3: - driver: local - pgdata4: - driver: local - pgdata5: - driver: local - pgdata6: - driver: local - pgdata7: - driver: local - pgdata8: - driver: local - pgdata9: - driver: local - pgdata10: - driver: local - pgdata11: - driver: local - pgdata12: - driver: local - pgdata13: - driver: local - pgdata14: - driver: local - pgdata15: - driver: local - dbeaver-data: - driver: local diff --git a/requester/src/app.ts b/requester/src/app.ts index c79b655..5837991 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -30,7 +30,7 @@ async function getRandomClient(): Promise { if (!randomClientInstance) { randomClientInstance = ((await RandomClient.defaultBuilder()) .withAOConfig(AO_CONFIG)) - .withProcessId("2ExUldxQ5NA_hnElSWYq0_lCBgeQQPxPhFbWDFihDEY") + // .withProcessId("2ExUldxQ5NA_hnElSWYq0_lCBgeQQPxPhFbWDFihDEY") .withWallet(JSON.parse(process.env.REQUEST_WALLET_JSON!)) .build(); } From 0057ad386efa00f8acc8713e19e5475857b9adad Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 4 Apr 2025 10:11:15 -0400 Subject: [PATCH 44/51] cleaned up2 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8e1d01f..2b9af51 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ venv/ env *wallet.json *wallet*.json +mass-docker-compose.yml # Ignore distribution/build directories dist/ build/ From 8c6c562a5d134ac3d1cf6b56462b4dc32d253688 Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 5 Apr 2025 14:13:44 -0400 Subject: [PATCH 45/51] Cleaned up docs --- README.md | 254 ++++-- orchestrator/package.json | 2 +- orchestrator/src/app.ts | 774 +----------------- orchestrator/src/containerManagment.ts | 322 ++++++++ orchestrator/src/db_config.ts | 7 - orchestrator/src/db_tools.ts | 80 ++ orchestrator/src/ecs_config.ts | 72 -- orchestrator/src/helperFunctions.ts | 396 +++++++++ .../src/{ => oldjunk}/clear_outputs.tzs | 0 orchestrator/src/{ => oldjunk}/stake.tzs | 0 orchestrator/src/reset_db.ts | 4 +- orchestrator/tsconfig.json | 2 +- requester/src/app.ts | 9 +- terraform/.debug_task.tf | 36 - terraform/README.md | 184 ----- terraform/ecs.tf | 315 ------- terraform/iam.json | 87 -- terraform/iam_roles.tf | 83 -- terraform/locals.tf | 23 - terraform/logging.tf | 6 - terraform/outputs.tf | 18 - terraform/package.json | 8 - terraform/providers.tf | 16 - terraform/rds.tf | 66 -- terraform/secrets.tf | 62 -- terraform/security.tf | 33 - terraform/terraform.tfvars.example | 31 - terraform/variables.tf | 61 -- terraform/vpc_endpoints.tf | 116 --- 29 files changed, 1011 insertions(+), 2056 deletions(-) create mode 100644 orchestrator/src/containerManagment.ts delete mode 100644 orchestrator/src/db_config.ts create mode 100644 orchestrator/src/db_tools.ts delete mode 100644 orchestrator/src/ecs_config.ts create mode 100644 orchestrator/src/helperFunctions.ts rename orchestrator/src/{ => oldjunk}/clear_outputs.tzs (100%) rename orchestrator/src/{ => oldjunk}/stake.tzs (100%) delete mode 100644 terraform/.debug_task.tf delete mode 100644 terraform/README.md delete mode 100644 terraform/ecs.tf delete mode 100644 terraform/iam.json delete mode 100644 terraform/iam_roles.tf delete mode 100644 terraform/locals.tf delete mode 100644 terraform/logging.tf delete mode 100644 terraform/outputs.tf delete mode 100644 terraform/package.json delete mode 100644 terraform/providers.tf delete mode 100644 terraform/rds.tf delete mode 100644 terraform/secrets.tf delete mode 100644 terraform/security.tf delete mode 100644 terraform/terraform.tfvars.example delete mode 100644 terraform/variables.tf delete mode 100644 terraform/vpc_endpoints.tf diff --git a/README.md b/README.md index 209ef8c..1f3cd0a 100644 --- a/README.md +++ b/README.md @@ -1,119 +1,235 @@ # Node Provider Setup Guide -This guide will walk you through getting your randomness provider set up and connected to the network so you can start contributing to the protocol and participating in decentralized randomness! +This guide will help you set up your randomness provider node and start earning rewards. The guide is split into a simple quickstart section followed by more detailed technical information. ## Table of Contents -1. [Introduction](#introduction) -2. [Hardware Requirements](#hardware-requirements) -3. [Randomness Generation](#randomness-generation) -4. [Deployment Options](#deployment-options) - - [Option 1: AWS Deployment with Terraform](#option-1-aws-deployment-with-terraform) - - [Option 2: Docker Compose Deployment](#option-2-docker-compose-deployment) -5. [Graceful Shutdown Policy](#graceful-shutdown-policy) -6. [Staking](#staking) +1. [Quickstart Guide](#quickstart-guide) +2. [How It Works](#how-it-works) +3. [Hardware Requirements](#hardware-requirements) +4. [Detailed Setup Instructions](#detailed-setup-instructions) +5. [Maintenance](#maintenance) +6. [Graceful Shutdown](#graceful-shutdown) +7. [Troubleshooting](#troubleshooting) +8. [Frequently Asked Questions](#frequently-asked-questions) --- -## Introduction -As a node provider, you are responsible for ensuring 100% uptime. In the event of necessary downtime, it is mandatory to run the graceful shutdown process to prevent being slashed. +## Quickstart Guide -Your provider performs 3 main functions: -1. It updates the amount of available random it has stored on chain. Each random value requires significant computation to create, ensuring compliance with our commit-reveal time delay scheme. -2. It detects when someone has requested random from you and provides the input number to your time delay function. This is not the final random number. -3. It detects when all parties have submitted their input numbers and then provides the output number along with the proof of history checkpoints to the chain for verification. This output is the random number that will be used on chain. - -These steps are executed as quickly as possible to get the complete random value on chain promptly. Faster providers will be incentivized for their speed, while slower ones will be penalized. If a provider is too slow for step 2, it will be slashed a small amount. If a provider is too slow for step 3, they will be considered malicious and slashed heavily. +Setting up your randomness provider is easy! Just follow these simple steps: -If you need to take your provider offline, you must run the graceful shutdown process, which will execute step 1 once with a value of -1, indicating you are no longer offering random. After that, your node will finish all pending requests in steps 2 and 3 before shutting down. +### Step 1: Install Docker +Install Docker and Docker Compose by following the [official Docker Compose installation guide](https://docs.docker.com/compose/install/) for your operating system. ---- +### Step 2: Set Up Your Environment +1. Navigate to the Docker Compose directory +2. Copy the example environment file: + ``` + cp .env.example .env + ``` +3. Edit the `.env` file and add your wallet information -## Hardware Requirements -To run a node, the following hardware specifications are required: - -- **Minimum Hardware Requirements:** - - 4 GB memory - - 2 CPU cores -- **Note:** These requirements will increase over time to meet network demands. - ---- +### Step 3: Start Your Provider +Run this command to start your provider: +``` +docker-compose up -d +``` -## Randomness Generation +### Step 4: Stake Your Node +1. Navigate to ar://randao +2. Connect your wallet +3. Follow the staking instructions to activate your provider -Our system now uses cryptographic time lock puzzles instead of Verifiable Delay Functions (VDF) for randomness generation. The provider "mines" for these puzzles, which creates a provable time delay between commitment and revelation of random values. This approach enhances security while maintaining verifiability of the randomness generated. +That's it! Your node is now running and will start generating randomness for the network. -The time lock puzzles require significant initial computation but become less resource-intensive once you've mined and stored a sufficient number of them for sale. This makes the provider more efficient over time as your puzzle inventory grows. +**Need help?** Check the [Troubleshooting](#troubleshooting) section or [Frequently Asked Questions](#frequently-asked-questions) below. --- -## What the Hardware Runs -The hardware you set up runs 3 key services: -1. A provider service -2. A database for the provider -3. Temporary jobs to generate random values and store them in the database +## How It Works -Both of our deployment options are designed to run these services efficiently. +Your provider performs 3 main functions: +1. It creates and stores random values that others can request +2. It responds when someone requests a random value +3. It submits final verified random values to the blockchain + +The better your provider performs these functions, the more rewards you'll receive. Providers with faster response times earn more! --- -## Deployment Options +## Hardware Requirements -We now fully support and recommend two deployment methods, depending on your infrastructure preferences and capabilities: +To run a node, you'll need: +- At least 4 GB memory +- At least 2 CPU cores +- Reliable internet connection -### Option 1: AWS Deployment with Terraform -This method leverages AWS services for optimal performance, scalability, and cost-effectiveness. +**Note:** These requirements may increase over time as the network grows. -**Advantages:** -- Most cost-effective solution at scale -- Guaranteed 100% uptime with AWS reliability -- Optimized for performance with managed services -- Automatic scaling based on demand +--- -While this option may be more technically complex to set up initially, it provides the best long-term solution for dedicated providers. +## Detailed Setup Instructions + +### Prerequisites +- A machine meeting the minimum hardware requirements +- Docker and Docker Compose installed +- Reliable internet connection +- Ability to ensure 100% uptime or follow the [Graceful Shutdown](#graceful-shutdown) procedures + +### Setup Steps + +1. **Configure Environment Variables** + Navigate to the Docker Compose directory and create your environment file: + ```bash + cp .env.example .env + ``` + + Then edit the `.env` file and fill in all required variables: + + **Required Variables:** + - `provider_id`: Your unique provider identifier + - `local_db_user`: Database username + - `local_db_password`: Database password + - `local_wallet_json`: Your wallet information (either directly pasted or as a file path) + + **Optional Variables (with defaults):** + - `db_name`: Database name (default: orchestrator_db) + - `secrets_prefix`: Prefix for secrets (default: /orchestrator) + +2. **Deploy Your Provider** + From the Docker Compose directory, run: + ```bash + docker-compose up -d + ``` + +3. **Verify Deployment** + Check the status of your containers: + ```bash + docker-compose ps + ``` + + View logs to ensure everything is running correctly: + ```bash + docker-compose logs -f + ``` + +4. **Staking** + After successfully setting up your node: + 1. Show logs from the node setup to Ethan for verification + 2. Upon confirmation, Ethan will provide your provider address with the necessary funds + 3. Navigate to ar://randao to stake your funds and configure your provider information -[Terraform setup guide](./terraform/README.md) +By completing this process, you will fully activate your node and ensure it is ready for network participation. -### Option 2: Docker Compose Deployment -This method allows you to run the provider on your own hardware using Docker containers. +--- -**Advantages:** -- Easier to set up if you have spare hardware available -- More straightforward for users familiar with Docker -- Direct control over your infrastructure -- Simpler technical requirements +## Maintenance -This option is less resource-intensive once you've mined enough time lock puzzles and stored them for sale. +To update your provider when new versions are released: -[Docker Compose setup guide](./docker-compose/README.md) +```bash +docker-compose pull +docker-compose down +docker-compose up -d +``` -Both methods are fully supported and recommended as long as you can ensure 100% uptime or follow the graceful shutdown procedure during maintenance periods. +Remember to follow the graceful shutdown procedure when performing maintenance to avoid penalties. --- -## Graceful Shutdown Policy -To avoid being slashed, it is critical to run the graceful shutdown in the event of planned downtime. Failing to do so may result in penalties. +## Graceful Shutdown + +If you need to perform maintenance or temporarily shut down your provider, it's critical to follow these steps to avoid being penalized: -To run a graceful shutdown: 1. Go to ar://randao 2. Navigate to your node 3. Select the "SHUT DOWN" button and sign the transaction - -This will tell your provider to stop serving random values. +4. Wait for your provider to complete all pending requests (check logs) +5. Once all pending requests are complete, you can safely shut down your provider After maintenance is complete and your provider is back online: 1. Click the "START UP" button 2. This will signal your provider to resume serving random values +**Warning:** Failing to follow the graceful shutdown procedure may result in penalties to your stake! + --- -## Staking +## Troubleshooting + +If you encounter issues with your provider, here are some common problems and solutions: + +### "Provider Not Found" Error +- Ensure your provider is properly staked at ar://randao +- Wait for blockchain confirmation (it may take some time for your stake to be recognized) +- Check your wallet configuration in the `.env` file + +### Network Connectivity Issues +- If your provider can't connect to the network, check your internet connection +- Check your firewall settings to ensure the required ports are open +- Wait for network conditions to improve before attempting to restart + +### Slow or Unresponsive Provider +- Check system resources to ensure your host has sufficient CPU and memory +- Monitor the logs for any error messages or warnings: + ```bash + docker-compose logs -f + ``` +- If the puzzle generator is struggling, consider scaling up your hardware + +### General Issues +- Try restarting the containers: + ```bash + docker-compose restart + ``` +- For more persistent issues, you can try a full reset: + ```bash + docker-compose down + docker-compose up -d + ``` +- Ensure your container has the latest version: + ```bash + docker-compose pull + docker-compose down + docker-compose up -d + ``` + +### Database Issues +- If the database container fails to start, check logs for specific errors: + ```bash + docker-compose logs db + ``` +- Ensure the database password in your `.env` file doesn't contain special characters that need escaping +- Verify database volume permissions if running on Linux -After successfully setting up your node, you will need to provide proof that the gateway is operational: +--- -1. Show logs from the node setup to Ethan for verification. -2. Upon confirmation, Ethan will provide your provider address with the necessary funds. -3. Navigate to ar://randao to stake your funds and configure your provider information. +## Frequently Asked Questions -By completing this process, you will fully activate your node and ensure it is ready for network participation. +### What is a randomness provider? +A randomness provider generates verifiable random numbers that are used in various decentralized applications on the blockchain. These random numbers are crucial for fair and transparent operation of many applications. + +### How do I earn rewards? +You earn rewards by providing random values to users who request them. The rewards depend on your provider's performance, reliability, and response time. + +### What happens if my provider goes offline? +If your provider goes offline without following the graceful shutdown procedure, you may be penalized (slashed). Always follow the [Graceful Shutdown](#graceful-shutdown) procedure before taking your provider offline. + +### How much can I earn as a provider? +Earnings depend on network demand, your provider's performance, and the amount you have staked. Better-performing providers with higher stakes tend to earn more. + +### Can I run multiple providers? +Yes, you can run multiple providers. Each provider needs its own unique wallet and must be staked separately. + +### What is the recommended hardware for optimal performance? +While the minimum requirements are 4GB RAM and 2 CPU cores, we recommend at least 8GB RAM and 4 CPU cores for optimal performance. + +### Do I need technical knowledge to run a provider? +Basic familiarity with command line operations and Docker is helpful, but the quickstart guide is designed to be accessible even to those with limited technical experience. + +### How often do I need to update my provider? +Updates will be announced in the community channels. We recommend keeping your provider updated to the latest version for optimal performance and security. + +--- Thank you for contributing to the network's success! diff --git a/orchestrator/package.json b/orchestrator/package.json index 2a04907..051e67b 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "^5.4.16", + "ao-process-clients": "^6.0.3", "ao-vrf": "file:", "arweave": "^1.15.5", "aws-sdk": "^2.1692.0", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 91875a3..f6c9c44 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -1,68 +1,33 @@ import { Client } from 'pg'; import Docker from 'dockerode'; import AWS from 'aws-sdk'; -import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, RandomClient} from "ao-process-clients" -import { dbConfig } from './db_config.js'; -import { getNetworkConfig, launchVDFTask, NetworkConfig } from './ecs_config'; +import { connectWithRetry, dbConfig, setupDatabase } from './db_tools.js'; import Arweave from 'arweave'; +import { cleanupFulfilledEntries, getProviderRequests, getRandomClient, processChallengeRequests, processOutputRequests } from './helperFunctions.js'; +import { checkAndFetchIfNeeded, monitorDockerContainers } from './containerManagment.js'; -const AO_CONFIG = { - MU_URL: "https://ur-mu.randao.net", - CU_URL: "https://ur-cu.randao.net", - GATEWAY_URL: "https://arweave.net", -}; - -let randomClientInstance: RandomClient | null = null; -let lastInitTime: number = 0; -const REINIT_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds - -async function getRandomClient(): Promise { - const currentTime = Date.now(); - - if (!randomClientInstance || (currentTime - lastInitTime) > REINIT_INTERVAL) { - randomClientInstance = ((await RandomClient.defaultBuilder()) - .withAOConfig(AO_CONFIG)) - .withWallet(JSON.parse(process.env.WALLET_JSON!)) - .build(); - lastInitTime = currentTime; - } - - return randomClientInstance; -} -// Optional: Auto-reinitialize on a timer -setInterval(() => { - randomClientInstance = null; -}, REINIT_INTERVAL); - - -const docker = new Docker(); -// Constants for configuration -const POLLING_INTERVAL_MS = 30000; //30 seconds -const DATABASE_CHECK_TIME = 60000; //60 seconds -const MINIMUM_ENTRIES = 1000; -const DRYRUNTIMEOUT = 30000; // 30 seconds -const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.1'; -const MAX_RETRIES = 10; -const RETRY_DELAY_MS = 10000; -const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; -const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); -const ongoingContainers = new Set(); // Track container IDs of running Docker containers -let PROVIDER_ID = ""; -const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "backend"; -const COMPLETION_RETENTION_PERIOD_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds -const UNCHAIN_VS_OFFCHAIN_MAX_DIF = 250; +export const docker = new Docker(); +export const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); +export const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; +export const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "backend"; +export const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.1'; -let ongoingRequest = false; -let spotInterruptions = 0; -// Global variables to track polling status +export const DOCKER_MONITORING_TIME= 30000; +export const POLLING_INTERVAL_MS = 30000; //30 seconds +export const DATABASE_CHECK_TIME = 60000; //60 seconds +export const MINIMUM_ENTRIES = 1000; +export const DRYRUNTIMEOUT = 30000; // 30 seconds +export const MAX_RETRIES = 10; +export const RETRY_DELAY_MS = 10000; +export const COMPLETION_RETENTION_PERIOD_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds +export const UNCHAIN_VS_OFFCHAIN_MAX_DIF = 250; + +let PROVIDER_ID = ""; let pollingInProgress = false; let lastPollingId: string | null = null; -let pulledDockerimage = false; -// Cache for network configuration -let cachedNetworkConfig: NetworkConfig | null = null; const arweave = Arweave.init({}); @@ -85,249 +50,6 @@ function resetStepTracking() { }; } -// Retry logic for connecting to PostgreSQL -async function connectWithRetry(): Promise { - const client = new Client(dbConfig); - for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { - try { - await client.connect(); - console.log(`Connected to PostgreSQL database (Attempt ${attempt})`); - return client; - } catch (error) { - console.error(`Connection attempt ${attempt} failed, retrying in ${RETRY_DELAY_MS / 1000} seconds...`); - await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS)); - } - } - throw new Error("Failed to connect to PostgreSQL after multiple attempts"); -} - -// Function to initialize the PostgreSQL database schema -async function setupDatabase(client: Client): Promise { - try { - // Drop old tables maybe - await client.query(` - DROP TABLE IF EXISTS verifiable_delay_functions CASCADE; - `); - - // Create the rsa_keys table - await client.query(` - CREATE TABLE rsa_keys ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - p TEXT NOT NULL, - q TEXT NOT NULL, - modulus TEXT NOT NULL UNIQUE, -- Store hex string of modulus N - phi TEXT NOT NULL - ); - `); - - // Create the time_lock_puzzles table with relation to rsa_keys based on rsa_id - await client.query(` -CREATE TABLE time_lock_puzzles ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - x TEXT NOT NULL, - y TEXT NOT NULL, - t TEXT NOT NULL, - modulus TEXT NOT NULL, -- Store hex string of modulus N - request_id TEXT NULL, - rsa_id UUID NOT NULL UNIQUE, - detected_completed TIMESTAMP NULL, -- Added to track completion time - FOREIGN KEY (rsa_id) REFERENCES rsa_keys(id) ON DELETE CASCADE -); - `); - - console.log("✅ Database setup complete. Tables are properly linked on rsa_id."); - } catch (error) { - console.error("❌ Error setting up database:", error); - } -} - - -async function triggerTimePuzzleJobPod(randomCount: number): Promise { - if (ENVIRONMENT === 'cloud') { - try { - console.log("Cloud environment detected. Launching ECS task."); - - // Get or refresh network configuration - if (!cachedNetworkConfig) { - console.log("Fetching network configuration..."); - cachedNetworkConfig = await getNetworkConfig(ecs); - console.log("Network config:", cachedNetworkConfig); - } - - const taskArn = await launchVDFTask(ecs, cachedNetworkConfig, randomCount); - if (taskArn) { - ongoingContainers.add(taskArn); - console.log(`ECS task started successfully: ${taskArn}`); - return taskArn; - } - return null; - } catch (error: any) { - if (error?.code === 'SpotCapacityNotAvailableException') { - spotInterruptions++; - console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); - } else { - console.error("Error launching ECS task:", error); - // Reset network config cache on error to force refresh on next attempt - cachedNetworkConfig = null; - } - return null; - } - } else { - const containerName = `puzzle-gen_job_${Date.now()}_${Math.floor(Math.random() * 100000)}`; - console.log(`Starting Docker container with name: ${containerName}`); - try { - if (!pulledDockerimage) { - console.log(`Pulling image: ${TIME_PUZZLE_JOB_IMAGE}`); - await new Promise((resolve, reject) => { - docker.pull(TIME_PUZZLE_JOB_IMAGE, (err: any, stream: NodeJS.ReadableStream) => { - if (err) { - return reject(err); - } - docker.modem.followProgress(stream, (doneErr) => { - if (doneErr) reject(doneErr); - else resolve(true); - }); - }); - }); - pulledDockerimage = true; - } - const container = await docker.createContainer({ - Image: TIME_PUZZLE_JOB_IMAGE, - Cmd: ['sh', '-c', `python3 main.py ${randomCount}`], - - Env: [ - `DATABASE_TYPE=postgresql`, - `DATABASE_HOST=${dbConfig.host}`, - `DATABASE_PORT=${dbConfig.port.toString()}`, - `DATABASE_USER=${dbConfig.user}`, - `DATABASE_PASSWORD=${dbConfig.password}`, - `DATABASE_NAME=${dbConfig.database}`, - ], - HostConfig: { - NetworkMode: DOCKER_NETWORK, - }, - name: containerName - }); - - await container.start(); - ongoingContainers.add(container.id); - console.log(`Docker container ${containerName} started successfully.`); - return container.id; - } catch (error) { - console.error(`Error starting Docker container ${containerName}:`, error); - return null; - } - } -} - - -// Modified function to wait for ECS tasks to complete and remove them from tracking -async function monitorECSTasks(): Promise { - if (ongoingContainers.size === 0) return; - - const describeTasksResult = await ecs.describeTasks({ - cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', - tasks: Array.from(ongoingContainers) - }).promise(); - - describeTasksResult.tasks?.forEach(task => { - // Check capacity provider name to determine if it is running Fargate or Fargate Spot - const capacityProvider = task.capacityProviderName || 'Unknown'; - - console.log(`ECS task: ${task.taskArn}, Capacity Provider: ${capacityProvider}`); - - if (task.lastStatus === 'STOPPED') { - console.log(`ECS task stopped: ${task.taskArn}`); - if (task.stoppedReason) { - console.log(`Task stopped reason: ${task.stoppedReason}`); - if (task.stoppedReason.includes('Host EC2 instance termination')) { - spotInterruptions++; - console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); - } - } else { - console.log('Task stopped due to normal completion.'); - } - ongoingContainers.delete(task.taskArn as string); - } - }); -} - -// Function to wait for Docker containers to complete and remove them from tracking -async function monitorDockerContainers(): Promise { - if (ongoingContainers.size === 0) return; - if (ENVIRONMENT === 'cloud') { - await monitorECSTasks(); - } else { - - for (const containerId of ongoingContainers) { - try { - const container = docker.getContainer(containerId); - const containerInfo = await container.inspect(); - - // Check if the container is already stopped (exited) - if (containerInfo.State.Status === 'exited') { - console.log(`Docker container stopped: ${containerId}`); - - // Attempt to remove the container, handling possible errors gracefully - try { - await container.remove({ force: true }); // Force removal to avoid "in progress" errors - console.log(`Docker container removed: ${containerId}`); - ongoingContainers.delete(containerId); - } catch (removeError) { - if (isDockerError(removeError) && removeError.statusCode === 409) { - // Error 409 means removal is in progress, so skip this container for now - console.log(`Removal of container ${containerId} is already in progress. Skipping.`); - } else { - // Handle other errors that might occur during container removal - console.error(`Error removing Docker container ${containerId}:`, removeError); - } - } - } - } catch (error) { - console.error(`Error inspecting Docker container ${containerId}:`, error); - ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) - } - } - } -} - -// Helper function to type guard Docker errors -function isDockerError(error: unknown): error is { statusCode: number } { - return typeof error === 'object' && error !== null && 'statusCode' in error && typeof (error as any).statusCode === 'number'; -} - -// Function to process hex output for 64-bit modulus -function hexMod64Bit(expectedOutput: string): { expectedOutput64BitBase10: string } { - // Parse the hexadecimal string into a BigInt - const number = BigInt(`${expectedOutput}`); - - // Define the 64-bit modulus (2^64 - 1) - const modulus = BigInt("0x7FFFFFFFF"); - - // Keep dividing by modulus until we get a remainder less than modulus - let remainder = number; - while (remainder >= modulus) { - remainder = remainder % modulus; - } - - // Return the remainder in base 10 - return { - expectedOutput64BitBase10: remainder.toString(), - }; -} - -function updateAvailableValuesAsync(currentCount: number) { - return (async () => { - try { - const randomClient = await getRandomClient(); - await randomClient.updateProviderAvailableValues(currentCount); - console.log(`Updated provider values to ${currentCount}`); - } catch (error) { - console.error("Failed to update provider values:", error); - } - })(); -} - async function shutdown() { try { @@ -341,168 +63,6 @@ async function shutdown() { } -async function getMoreRandom(currentCount: number) { - const entriesNeeded = MINIMUM_ENTRIES - currentCount; - console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries...`); - - ongoingRequest = true; - - if (ongoingContainers.size > 0) { - console.log("A puzzle-gen container is already running. Skipping new container launch."); - return null; - } - - console.log(`Spawning a single container to generate ${entriesNeeded} random values.`); - - try { - const jobId = await triggerTimePuzzleJobPod(entriesNeeded); - if (jobId) { - console.log(`Job triggered: ${jobId}`); - ongoingContainers.add(jobId); - } - } catch (error) { - console.error('Error triggering job pod:', error); - } -} - - - -// Function to check and fetch database entries as needed -async function checkAndFetchIfNeeded(client: Client) { - try { - //Check if provider has been given a special signal - const on_chain_avalible_random = await getProviderAvailableRandomValues(PROVIDER_ID); - // Query current count of usable DB entries - const res = await client.query( - 'SELECT COUNT(*) AS count FROM time_lock_puzzles WHERE request_id IS NULL' - ); - const currentCount = parseInt(res.rows[0].count, 10); - console.log("Total usable DB entries: " + currentCount); - - switch (on_chain_avalible_random.availibleRandomValues) { - case -1: - console.log("Value is -1"); - console.log("Provider has been shut down by USER..."); - console.log("Go to the provider dashboard to turn back on"); - //TODO prepare for shutdown - //TODO the async causes it to ovewrite itself - break; - case -2: - console.log("Value is -2"); - console.log("Provider has been shut down by PROCESS..."); - console.log("This is due to One of the following: "); - console.log("Failing to provide random fast enough (Provider is not responding to random requests and considered unhealthy NO SLASH)"); - console.log("Failing to provide the proof for an outstanding random request within the time. (Provider was likely turned off mid random request SMALL SLASH)" ); - console.log("Failing to provide the correct proof for your original random (Provider was detected as malicious for tampering with the random LARGE SLASH)"); - console.log("Go to the provider dashboard to turn back on"); - break; - case -3: - console.log("Value is -3"); - console.log("Provider has been shut down by PROCESS..."); - console.log("This was likely done as a test or to get the maintainers attention. Contact team if you see this and are not sure why"); - console.log("Go to the provider dashboard to turn back on"); - break; - default: - console.log("Value is not -1, -2, or -3"); - console.log("Provider is up and working"); - console.log("Onchain Value is "+ on_chain_avalible_random.availibleRandomValues) - console.log("Local Value is "+ currentCount) - if (Math.abs(on_chain_avalible_random.availibleRandomValues - currentCount) > UNCHAIN_VS_OFFCHAIN_MAX_DIF) { - console.log(`Updating available random values from ${on_chain_avalible_random.availibleRandomValues} to ${currentCount}`); - updateAvailableValuesAsync(currentCount); - } - } - if (ongoingRequest) return; // Prevent redundant operations - - // Check if more entries are needed - if (currentCount >= MINIMUM_ENTRIES) return; - getMoreRandom(currentCount) - - } catch (error) { - console.error('Error during check and fetch:', error); - } finally { - ongoingRequest = false; // Allow future operations - } -} - -// Function to post VDF challenge (fetches dbId dynamically) -async function fulfillRandomChallenge(client: Client, requestId: string, parentLogId: string): Promise { - try { - // Fetch the necessary details from the database using requestId - const res = await client.query( - `SELECT id, modulus, x - FROM time_lock_puzzles - WHERE request_id = $1`, - [requestId] - ); - - if (!res.rowCount) { - console.error(`No entry found for Request ID: ${requestId}`); - return; - } - - const { id: dbId, modulus, x: input } = res.rows[0]; - - console.log(`${parentLogId} Fetched entry details - Request ID: ${requestId}, DB ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); - - console.log(`${parentLogId} Posting VDF challenge for Request ID: ${requestId}, DB ID: ${dbId}`); - await (await getRandomClient()).commit({ - requestId: requestId, - puzzle: { - input: input, - modulus: modulus - } - }); - console.log(`${parentLogId} Challenge posted for Request ID: ${requestId}. Waiting to post proof...`); - } catch (error) { - console.error(`${parentLogId} Error posting VDF challenge for Request ID: ${requestId}:`, error); - } -} - - - -// Function to post VDF output and proof -async function fulfillRandomOutput(client: Client, requestId: string, parentLogId: string): Promise { - try { - // Fetch the output and proof from the database using the requestId - const res = await client.query( - `SELECT - tlp.id, - tlp.y AS output, - rk.p, - rk.q - FROM time_lock_puzzles tlp - JOIN rsa_keys rk ON tlp.rsa_id = rk.id - WHERE tlp.request_id = $1`, - [requestId] - ); - - if (!res.rowCount) { - console.error(`No entry found for request ID: ${requestId}`); - return; - } - // Map the response to structured variables -const { - id: dbId, - output, // Mapping 'y' to 'output' - p: rsaP, - q: rsaQ -} = res.rows[0]; - console.log(`${parentLogId} Fetched entry from database for output - ID: ${dbId}, requestID: ${requestId}, output: ${output}, rsaP: ${rsaP}, rsaQ ${rsaQ} `); - - console.log(`${parentLogId} Posting VDF output and proof for - ID: ${dbId}, request ID: ${requestId}`); - await (await getRandomClient()).reveal({ - requestId: requestId, - rsa_key: { - p: rsaP, - q: rsaQ - } - }) - console.log(`${parentLogId} Proof posted for request ID: ${requestId}`); - } catch (error) { - console.error(`${parentLogId} Error fulfilling random output for request ID: ${requestId}:`, error); - } -} function getLogId(): string { const randomId = Math.floor(10000 + Math.random() * 90000); // 5-digit random number @@ -510,81 +70,6 @@ function getLogId(): string { return `[LogID: ${randomId} | ${timestamp}]`; } -async function getProviderRequests(PROVIDER_ID: string, parentLogId: string): Promise { - - let openRequests: GetOpenRandomRequestsResponse; - - // Create a function to fetch open requests with a timeout - const fetchOpenRequests = async (): Promise => { - try { - const response = await (await getRandomClient()).getOpenRandomRequests(PROVIDER_ID); - return response; - } catch (error) { - console.error(`${parentLogId} Error fetching requests: ${error}`); - return { /* Return a default or empty response here */ } as GetOpenRandomRequestsResponse; - } - }; - - try { - openRequests = await Promise.race([ - fetchOpenRequests().catch(err => { - throw new Error(`${parentLogId} Fetch Error: ${err}`); - }), - new Promise((_, reject) => - setTimeout(() => reject(new Error('Timeout')), DRYRUNTIMEOUT) - ) - ]); - } catch (error) { - console.log(`${parentLogId} Step 1: ${error}`); - //randclient.setDryRunAsMessage(true); - console.log("Removed this as its spamming") - console.log("Switching dryrun off"); - openRequests = await fetchOpenRequests(); // Retry request - } - - if(openRequests.toString().includes("not found")){ - return false - } - console.log(`${parentLogId} Step 1: Open Requests: ${JSON.stringify(openRequests)}`); - console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${openRequests.activeChallengeRequests.request_ids.length}`); - console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${openRequests.activeOutputRequests.request_ids.length}`); - return openRequests; -} - -async function getProviderAvailableRandomValues(PROVIDER_ID: string): Promise { - let avalibleRandom: GetProviderAvailableValuesResponse; - // Create a function to fetch open requests with a timeout - const fetchAvalibleRandom = async (): Promise => { - try { - const response = await (await getRandomClient()).getProviderAvailableValues(PROVIDER_ID); - return response; - } catch (error) { - console.error(`Error fetching avalible random: ${error}`); - return { /* Return a default or empty response here */ } as GetProviderAvailableValuesResponse; - } - }; - - try { - avalibleRandom = await Promise.race([ - fetchAvalibleRandom().catch(err => { - throw new Error(`Fetch Error: ${err}`); - }), - new Promise((_, reject) => - setTimeout(() => reject(new Error('Timeout')), DRYRUNTIMEOUT) - ) - ]); - } catch (error) { - // console.log(`${parentLogId} Step 1: ${error}`); - //randclient.setDryRunAsMessage(true); - console.log("Removed this as its spamming") - console.log("Switching dryrun off"); - avalibleRandom = await fetchAvalibleRandom(); // Retry request - } - return avalibleRandom; -} - - - async function polling(client: any) { if (pollingInProgress) { const completedSteps = Object.entries(stepTracking) @@ -636,9 +121,8 @@ async function polling(client: any) { (async () => { const s4 = Date.now(); console.log(`${logId} Step 4 started.`); - //TODO enable this again later - //await cleanupFulfilledEntries(client, openRequests, logId); + await cleanupFulfilledEntries(client, openRequests, logId); stepTracking.step4 = { completed: true, timeTaken: Date.now() - s4 }; console.log(`${logId} Step 4 completed. Time taken: ${stepTracking.step4.timeTaken}ms`); })(), @@ -656,212 +140,6 @@ async function polling(client: any) { -// Step 2: Process Challenge Requests (Database selection & assigning is atomic) -async function processChallengeRequests( - client: Client, - activeChallengeRequests: { request_ids: string[] } | undefined, - parentLogId: string -): Promise { - console.log(`${parentLogId} Step 2: Processing challenge requests.`); - - if (!activeChallengeRequests || activeChallengeRequests.request_ids.length === 0) { - console.log(`${parentLogId} No Challenge Requests to process.`); - return; - } - - const requestIds = activeChallengeRequests.request_ids; - console.log(`${parentLogId} Processing up to ${requestIds.length} requests.`); - - try { - await client.query('BEGIN'); // Start transaction - - console.log(`${parentLogId} Fetching existing request mappings.`); - - // Fetch already assigned request_id -> dbId mappings - const existingMappingsRes = await client.query( - `SELECT request_id FROM time_lock_puzzles - WHERE request_id = ANY($1) - FOR UPDATE SKIP LOCKED`, - [requestIds] - ); - - const existingRequestIds = new Set(existingMappingsRes.rows.map(row => row.request_id)); - console.log(`${parentLogId} Found ${existingRequestIds.size} already mapped requests.`); - - // Find only the unmapped requests (requestIds not in existingRequestIds) - const unmappedRequestIds = requestIds.filter(requestId => !existingRequestIds.has(requestId)); - console.log(`${parentLogId} Unmapped requests: ${unmappedRequestIds.length}`); - - let mappedEntries: { requestId: string, dbId: number }[] = []; - - if (unmappedRequestIds.length > 0) { - console.log(`${parentLogId} Fetching available DB entries.`); - const dbRes = await client.query( - `SELECT id FROM time_lock_puzzles - WHERE request_id IS NULL - ORDER BY id ASC - LIMIT $1 - FOR UPDATE SKIP LOCKED`, - [unmappedRequestIds.length] - ); - - const availableDbEntries = dbRes.rows.map(row => row.id); - console.log(`${parentLogId} Found ${availableDbEntries.length} available DB entries.`); - - if (availableDbEntries.length > 0) { - const numMappings = Math.min(unmappedRequestIds.length, availableDbEntries.length); - - for (let i = 0; i < numMappings; i++) { - await client.query( - `UPDATE time_lock_puzzles - SET request_id = $1 - WHERE id = $2`, - [unmappedRequestIds[i], availableDbEntries[i]] - ); - mappedEntries.push({ requestId: unmappedRequestIds[i], dbId: availableDbEntries[i] }); - console.log(`${parentLogId} Assigned Request ID ${unmappedRequestIds[i]} to DB Entry ${availableDbEntries[i]}.`); - } - } else { - console.log(`${parentLogId} No available DB entries for unmapped requests.`); - } - } - - // Collect all request IDs (previously mapped + newly mapped) - const allRequestIds = [...existingRequestIds, ...mappedEntries.map(entry => entry.requestId)]; - - if (allRequestIds.length === 0) { - console.log(`${parentLogId} No requests to process. Committing transaction.`); - await client.query('COMMIT'); - return; - } - - await client.query('COMMIT'); // Commit all updates at once - console.log(`${parentLogId} Committed all changes. Now fulfilling challenges.`); - - // Call fulfillRandomChallenge for all request IDs - await Promise.all( - allRequestIds.map(requestId => - fulfillRandomChallenge(client, requestId, parentLogId) - .catch(error => console.error(`${parentLogId} Error fulfilling challenge for Request ID ${requestId}:`, error)) - ) - ); - - console.log(`${parentLogId} All challenges fulfilled`); - } catch (error:any) { - console.error(`${parentLogId} Error in processChallengeRequests:`, error); - await client.query('ROLLBACK'); // Rollback on failure - - console.error(`SQL State: ${error.code}, Message: ${error.message}`); - } - - -} - -// Step 3: Process Output Requests (unchanged but with logging) -async function processOutputRequests( - client: Client, - activeOutputRequests: { request_ids: string[] } | undefined, - parentLogId: string -): Promise { - console.log(`${parentLogId} Step 3: Processing output requests.`); - - if (!activeOutputRequests || activeOutputRequests.request_ids.length === 0) { - console.log(`${parentLogId} No Output Requests to process.`); - return; - } - - const outputPromises = activeOutputRequests.request_ids.map(async (requestId) => { - console.log(`${parentLogId} Processing output request ID: ${requestId}`); - - // Run fulfillRandomOutput asynchronously (do not await) - fulfillRandomOutput(client, requestId, parentLogId) - .catch(error => console.error(`${parentLogId} Error fulfilling output:`, error)); - }); - - await Promise.all(outputPromises); - console.log(`${parentLogId} Step 3 completed.`); -} - -// Step 4: Remove fulfilled entries no longer in use (unchanged but with logging) -async function cleanupFulfilledEntries( - client: Client, - openRequests: any, - parentLogId: string -): Promise { - console.log(`${parentLogId} Step 4: Checking for fulfilled entries no longer in use.`); - - const now = new Date(); - const cutoffTime = new Date(now.getTime() - COMPLETION_RETENTION_PERIOD_MS); - - try { - await client.query('BEGIN'); - - // Fetch all entries with a request_id - const result = await client.query(` - SELECT id, request_id, detected_completed - FROM time_lock_puzzles - WHERE request_id IS NOT NULL - `); - - let markForDeletion: string[] = []; - let markAsCompleted: string[] = []; - - for (const row of result.rows) { - const { id, request_id, detected_completed } = row; - - // Check if this request is still active in challenge or output - const isStillInChallenge = openRequests.activeChallengeRequests?.request_ids.includes(request_id); - const isStillInOutput = openRequests.activeOutputRequests?.request_ids.includes(request_id); - - if (!isStillInChallenge && !isStillInOutput) { - if (!detected_completed) { - // Mark it for deletion by setting detected_completed timestamp - markAsCompleted.push(id); - } else if (new Date(detected_completed) < cutoffTime) { - // If already marked and older than retention period, delete it - markForDeletion.push(id); - } - } - } - - // Mark entries as completed - if (markAsCompleted.length > 0) { - await client.query(` - UPDATE time_lock_puzzles - SET detected_completed = NOW() - WHERE id = ANY($1) - `, [markAsCompleted]); - console.log(`${parentLogId} Marked ${markAsCompleted.length} entries as completed.`); - } - - // Delete old completed entries //TODO make sure its cleaning up BOTH tables - if (markForDeletion.length > 0) { - await client.query(` - DELETE FROM rsa_keys - WHERE id IN ( - SELECT rsa_id FROM time_lock_puzzles WHERE id = ANY($1) - ); - `, [markForDeletion]); - - await client.query(` - DELETE FROM time_lock_puzzles - WHERE id = ANY($1); - `, [markForDeletion]); - - console.log(`${parentLogId} Deleted ${markForDeletion.length} old completed entries and corresponding RSA keys.`); - } - - await client.query('COMMIT'); - } catch (error) { - console.error(`${parentLogId} Error in cleanupFulfilledEntries:`, error); - await client.query('ROLLBACK'); - } - - console.log(`${parentLogId} Step 4 completed.`); -} - - - // Main function async function run(): Promise { const client = await connectWithRetry(); @@ -870,7 +148,6 @@ async function run(): Promise { arweave.wallets.jwkToAddress(JSON.parse(process.env.WALLET_JSON!)).then((address) => { console.log(address); PROVIDER_ID = address - //1seRanklLU_1VTGkEk7P0xAwMJfA7owA1JHW5KyZKlY }); setInterval(async () => { @@ -878,7 +155,7 @@ async function run(): Promise { console.log(`Periodic log - Current database size: ${res.rows[0].count}`); // Check and fetch entries for the database if needed console.log("Step 0: Checking and fetching database entries if below threshold."); - checkAndFetchIfNeeded(client).catch((error) => { + checkAndFetchIfNeeded(client, PROVIDER_ID).catch((error) => { console.error("Error in checkAndFetchIfNeeded:", error); }); @@ -886,18 +163,12 @@ async function run(): Promise { setInterval(async () => { await monitorDockerContainers(); - }, 30000); // Cleanup every 30 seconds + }, DOCKER_MONITORING_TIME); // Cleanup every 30 seconds setInterval(async () => { await polling(client); }, POLLING_INTERVAL_MS); - // setInterval(async () => { - // randclient.setDryRunAsMessage(false); - // console.log("Switching dryrun on") - // }, DRYRUNRESETTIME); - - process.on("SIGTERM", async () => { console.log("SIGTERM received. Closing database connection."); await client.end(); @@ -907,3 +178,4 @@ async function run(): Promise { } run().catch((err) => console.error(`Error in main function: ${err}`)); + diff --git a/orchestrator/src/containerManagment.ts b/orchestrator/src/containerManagment.ts new file mode 100644 index 0000000..438a149 --- /dev/null +++ b/orchestrator/src/containerManagment.ts @@ -0,0 +1,322 @@ +import { docker, DOCKER_NETWORK, ecs, ENVIRONMENT, MINIMUM_ENTRIES, TIME_PUZZLE_JOB_IMAGE, UNCHAIN_VS_OFFCHAIN_MAX_DIF } from "./app"; +import AWS from 'aws-sdk'; +import { dbConfig } from "./db_tools"; +import { getProviderAvailableRandomValues, updateAvailableValuesAsync } from "./helperFunctions"; +import { Client } from "pg"; + +export interface NetworkConfig { + subnets: string[]; + securityGroups: string[]; +} + + +let ongoingRequest = false; +let spotInterruptions = 0; +// Global variables to track polling status +let pulledDockerimage = false; +// Cache for network configuration +let cachedNetworkConfig: NetworkConfig | null = null; +const ongoingContainers = new Set(); // Track container IDs of running Docker containers + +export async function getNetworkConfig(ecs: AWS.ECS): Promise { + // Get the task definition to extract network configuration + const taskDef = await ecs.describeTaskDefinition({ taskDefinition: 'vdf-job' }).promise(); + + // Get the service to extract network configuration + const services = await ecs.listServices({ cluster: process.env.ECS_CLUSTER_NAME }).promise(); + const serviceArn = services.serviceArns?.find(arn => arn.includes('vdf-job-service')); + + if (!serviceArn) { + throw new Error('VDF job service not found'); + } + + const service = await ecs.describeServices({ + cluster: process.env.ECS_CLUSTER_NAME, + services: [serviceArn] + }).promise(); + + const networkConfig = service.services?.[0]?.networkConfiguration?.awsvpcConfiguration; + + if (!networkConfig?.subnets || !networkConfig?.securityGroups) { + throw new Error('Network configuration not found'); + } + + return { + subnets: networkConfig.subnets, + securityGroups: networkConfig.securityGroups + }; +} + +export async function launchVDFTask( + ecs: AWS.ECS, + networkConfig: NetworkConfig, + random_per_vdf: number, +): Promise { + const result = await ecs.runTask({ + cluster: process.env.ECS_CLUSTER_NAME, + taskDefinition: 'vdf-job', + capacityProviderStrategy: [ + { + capacityProvider: 'FARGATE_SPOT', + weight: 1, + }, + ], + networkConfiguration: { + awsvpcConfiguration: { + subnets: networkConfig.subnets, + securityGroups: networkConfig.securityGroups, + assignPublicIp: 'ENABLED', + }, + }, + count: 1, + overrides: { + containerOverrides: [ + { + name: 'vdf_job_container', // Must match the container name in the task definition + command: ['sh', '-c', `python3 main.py ${random_per_vdf}`], + }, + ], + }, + }).promise(); + + const taskArn = result.tasks?.[0]?.taskArn; + return taskArn || null; +} + + + + +export async function triggerTimePuzzleJobPod(randomCount: number): Promise { + if (ENVIRONMENT === 'cloud') { + try { + console.log("Cloud environment detected. Launching ECS task."); + + // Get or refresh network configuration + if (!cachedNetworkConfig) { + console.log("Fetching network configuration..."); + cachedNetworkConfig = await getNetworkConfig(ecs); + console.log("Network config:", cachedNetworkConfig); + } + + const taskArn = await launchVDFTask(ecs, cachedNetworkConfig, randomCount); + if (taskArn) { + ongoingContainers.add(taskArn); + console.log(`ECS task started successfully: ${taskArn}`); + return taskArn; + } + return null; + } catch (error: any) { + if (error?.code === 'SpotCapacityNotAvailableException') { + spotInterruptions++; + console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); + } else { + console.error("Error launching ECS task:", error); + // Reset network config cache on error to force refresh on next attempt + cachedNetworkConfig = null; + } + return null; + } + } else { + const containerName = `puzzle-gen_job_${Date.now()}_${Math.floor(Math.random() * 100000)}`; + console.log(`Starting Docker container with name: ${containerName}`); + try { + if (!pulledDockerimage) { + console.log(`Pulling image: ${TIME_PUZZLE_JOB_IMAGE}`); + await new Promise((resolve, reject) => { + docker.pull(TIME_PUZZLE_JOB_IMAGE, (err: any, stream: NodeJS.ReadableStream) => { + if (err) { + return reject(err); + } + docker.modem.followProgress(stream, (doneErr) => { + if (doneErr) reject(doneErr); + else resolve(true); + }); + }); + }); + pulledDockerimage = true; + } + const container = await docker.createContainer({ + Image: TIME_PUZZLE_JOB_IMAGE, + Cmd: ['sh', '-c', `python3 main.py ${randomCount}`], + + Env: [ + `DATABASE_TYPE=postgresql`, + `DATABASE_HOST=${dbConfig.host}`, + `DATABASE_PORT=${dbConfig.port.toString()}`, + `DATABASE_USER=${dbConfig.user}`, + `DATABASE_PASSWORD=${dbConfig.password}`, + `DATABASE_NAME=${dbConfig.database}`, + ], + HostConfig: { + NetworkMode: DOCKER_NETWORK, + }, + name: containerName + }); + + await container.start(); + ongoingContainers.add(container.id); + console.log(`Docker container ${containerName} started successfully.`); + return container.id; + } catch (error) { + console.error(`Error starting Docker container ${containerName}:`, error); + return null; + } + } +} + +export async function getMoreRandom(currentCount: number) { + const entriesNeeded = MINIMUM_ENTRIES - currentCount; + console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries...`); + + ongoingRequest = true; + + if (ongoingContainers.size > 0) { + console.log("A puzzle-gen container is already running. Skipping new container launch."); + return null; + } + + console.log(`Spawning a single container to generate ${entriesNeeded} random values.`); + + try { + const jobId = await triggerTimePuzzleJobPod(entriesNeeded); + if (jobId) { + console.log(`Job triggered: ${jobId}`); + ongoingContainers.add(jobId); + } + } catch (error) { + console.error('Error triggering job pod:', error); + } +} + +// Modified function to wait for ECS tasks to complete and remove them from tracking +async function monitorECSTasks(): Promise { + if (ongoingContainers.size === 0) return; + + const describeTasksResult = await ecs.describeTasks({ + cluster: process.env.ECS_CLUSTER_NAME || 'fargate-cluster', + tasks: Array.from(ongoingContainers) + }).promise(); + + describeTasksResult.tasks?.forEach(task => { + // Check capacity provider name to determine if it is running Fargate or Fargate Spot + const capacityProvider = task.capacityProviderName || 'Unknown'; + + console.log(`ECS task: ${task.taskArn}, Capacity Provider: ${capacityProvider}`); + + if (task.lastStatus === 'STOPPED') { + console.log(`ECS task stopped: ${task.taskArn}`); + if (task.stoppedReason) { + console.log(`Task stopped reason: ${task.stoppedReason}`); + if (task.stoppedReason.includes('Host EC2 instance termination')) { + spotInterruptions++; + console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); + } + } else { + console.log('Task stopped due to normal completion.'); + } + ongoingContainers.delete(task.taskArn as string); + } + }); +} + +// Function to wait for Docker containers to complete and remove them from tracking +export async function monitorDockerContainers(): Promise { + if (ongoingContainers.size === 0) return; + if (ENVIRONMENT === 'cloud') { + await monitorECSTasks(); + } else { + + for (const containerId of ongoingContainers) { + try { + const container = docker.getContainer(containerId); + const containerInfo = await container.inspect(); + + // Check if the container is already stopped (exited) + if (containerInfo.State.Status === 'exited') { + console.log(`Docker container stopped: ${containerId}`); + + // Attempt to remove the container, handling possible errors gracefully + try { + await container.remove({ force: true }); // Force removal to avoid "in progress" errors + console.log(`Docker container removed: ${containerId}`); + ongoingContainers.delete(containerId); + } catch (removeError) { + if (isDockerError(removeError) && removeError.statusCode === 409) { + // Error 409 means removal is in progress, so skip this container for now + console.log(`Removal of container ${containerId} is already in progress. Skipping.`); + } else { + // Handle other errors that might occur during container removal + console.error(`Error removing Docker container ${containerId}:`, removeError); + } + } + } + } catch (error) { + console.error(`Error inspecting Docker container ${containerId}:`, error); + ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) + } + } + } +} + +// Helper function to type guard Docker errors +function isDockerError(error: unknown): error is { statusCode: number } { + return typeof error === 'object' && error !== null && 'statusCode' in error && typeof (error as any).statusCode === 'number'; +} + +// Function to check and fetch database entries as needed +export async function checkAndFetchIfNeeded(client: Client, PROVIDER_ID:string) { + try { + //Check if provider has been given a special signal + const on_chain_avalible_random = await getProviderAvailableRandomValues(PROVIDER_ID); + // Query current count of usable DB entries + const res = await client.query( + 'SELECT COUNT(*) AS count FROM time_lock_puzzles WHERE request_id IS NULL' + ); + const currentCount = parseInt(res.rows[0].count, 10); + console.log("Total usable DB entries: " + currentCount); + + switch (on_chain_avalible_random.availibleRandomValues) { + case -1: + console.log("Value is -1"); + console.log("Provider has been shut down by USER..."); + console.log("Go to the provider dashboard to turn back on"); + //TODO prepare for shutdown + //TODO the async causes it to ovewrite itself + break; + case -2: + console.log("Value is -2"); + console.log("Provider has been shut down by PROCESS..."); + console.log("This is due to One of the following: "); + console.log("Failing to provide random fast enough (Provider is not responding to random requests and considered unhealthy NO SLASH)"); + console.log("Failing to provide the proof for an outstanding random request within the time. (Provider was likely turned off mid random request SMALL SLASH)" ); + console.log("Failing to provide the correct proof for your original random (Provider was detected as malicious for tampering with the random LARGE SLASH)"); + console.log("Go to the provider dashboard to turn back on"); + break; + case -3: + console.log("Value is -3"); + console.log("Provider has been shut down by PROCESS..."); + console.log("This was likely done as a test or to get the maintainers attention. Contact team if you see this and are not sure why"); + console.log("Go to the provider dashboard to turn back on"); + break; + default: + console.log("Value is not -1, -2, or -3"); + console.log("Provider is up and working"); + console.log("Onchain Value is "+ on_chain_avalible_random.availibleRandomValues) + console.log("Local Value is "+ currentCount) + if (Math.abs(on_chain_avalible_random.availibleRandomValues - currentCount) > UNCHAIN_VS_OFFCHAIN_MAX_DIF) { + console.log(`Updating available random values from ${on_chain_avalible_random.availibleRandomValues} to ${currentCount}`); + updateAvailableValuesAsync(currentCount); + } + } + if (ongoingRequest) return; // Prevent redundant operations + + // Check if more entries are needed + if (currentCount >= MINIMUM_ENTRIES) return; + getMoreRandom(currentCount) + + } catch (error) { + console.error('Error during check and fetch:', error); + } finally { + ongoingRequest = false; // Allow future operations + } +} \ No newline at end of file diff --git a/orchestrator/src/db_config.ts b/orchestrator/src/db_config.ts deleted file mode 100644 index 22c50ca..0000000 --- a/orchestrator/src/db_config.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const dbConfig = { - host: process.env.DB_HOST || 'localhost', - port: parseInt(process.env.DB_PORT || '5432', 10), - user: process.env.DB_USER || 'myuser', - password: process.env.DB_PASSWORD || 'mypassword', - database: process.env.DB_NAME || 'mydatabase', -}; \ No newline at end of file diff --git a/orchestrator/src/db_tools.ts b/orchestrator/src/db_tools.ts new file mode 100644 index 0000000..ec93622 --- /dev/null +++ b/orchestrator/src/db_tools.ts @@ -0,0 +1,80 @@ +import { Client } from "pg"; +import { MAX_RETRIES, RETRY_DELAY_MS } from "./app"; + +export const dbConfig = { + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT || '5432', 10), + user: process.env.DB_USER || 'myuser', + password: process.env.DB_PASSWORD || 'mypassword', + database: process.env.DB_NAME || 'mydatabase', +}; + +// Clear out the existing database +export async function clearDatabase(client: Client): Promise { + console.log("Clearing database..."); + + await client.query(`SET session_replication_role = 'replica';`); + + const { rows } = await client.query(`SELECT tablename FROM pg_tables WHERE schemaname = 'public';`); + for (const row of rows) { + console.log(`Dropping table: ${row.tablename}`); + await client.query(`DROP TABLE IF EXISTS "${row.tablename}" CASCADE;`); + } + + await client.query(`SET session_replication_role = 'origin';`); + + console.log("Database cleared."); +} + +export async function setupDatabase(client: Client): Promise { + try { + // Create rsa_keys table if it doesn't exist + await client.query(` + CREATE TABLE IF NOT EXISTS rsa_keys ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + p TEXT NOT NULL, + q TEXT NOT NULL, + modulus TEXT NOT NULL UNIQUE, + phi TEXT NOT NULL + ); + `); + + // Create time_lock_puzzles table if it doesn't exist + await client.query(` + CREATE TABLE IF NOT EXISTS time_lock_puzzles ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + x TEXT NOT NULL, + y TEXT NOT NULL, + t TEXT NOT NULL, + modulus TEXT NOT NULL, + request_id TEXT NULL, + rsa_id UUID NOT NULL UNIQUE, + detected_completed TIMESTAMP NULL, + FOREIGN KEY (rsa_id) REFERENCES rsa_keys(id) ON DELETE CASCADE + ); + `); + + // Drop the old verifiable_delay_functions table if exists + await client.query(`DROP TABLE IF EXISTS verifiable_delay_functions CASCADE;`); + + console.log("✅ Database setup complete or already exists."); + } catch (error: any) { + console.error("❌ Legitimate issue encountered during database setup:", error.message); + } +} + +// Retry logic for connecting to PostgreSQL +export async function connectWithRetry(): Promise { + const client = new Client(dbConfig); + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + await client.connect(); + console.log(`Connected to PostgreSQL database (Attempt ${attempt})`); + return client; + } catch (error) { + console.error(`Connection attempt ${attempt} failed, retrying in ${RETRY_DELAY_MS / 1000} seconds...`); + await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS)); + } + } + throw new Error("Failed to connect to PostgreSQL after multiple attempts"); +} \ No newline at end of file diff --git a/orchestrator/src/ecs_config.ts b/orchestrator/src/ecs_config.ts deleted file mode 100644 index 5bd863e..0000000 --- a/orchestrator/src/ecs_config.ts +++ /dev/null @@ -1,72 +0,0 @@ -import AWS from 'aws-sdk'; - -export interface NetworkConfig { - subnets: string[]; - securityGroups: string[]; -} - -export async function getNetworkConfig(ecs: AWS.ECS): Promise { - // Get the task definition to extract network configuration - const taskDef = await ecs.describeTaskDefinition({ taskDefinition: 'vdf-job' }).promise(); - - // Get the service to extract network configuration - const services = await ecs.listServices({ cluster: process.env.ECS_CLUSTER_NAME }).promise(); - const serviceArn = services.serviceArns?.find(arn => arn.includes('vdf-job-service')); - - if (!serviceArn) { - throw new Error('VDF job service not found'); - } - - const service = await ecs.describeServices({ - cluster: process.env.ECS_CLUSTER_NAME, - services: [serviceArn] - }).promise(); - - const networkConfig = service.services?.[0]?.networkConfiguration?.awsvpcConfiguration; - - if (!networkConfig?.subnets || !networkConfig?.securityGroups) { - throw new Error('Network configuration not found'); - } - - return { - subnets: networkConfig.subnets, - securityGroups: networkConfig.securityGroups - }; -} - -export async function launchVDFTask( - ecs: AWS.ECS, - networkConfig: NetworkConfig, - random_per_vdf: number, -): Promise { - const result = await ecs.runTask({ - cluster: process.env.ECS_CLUSTER_NAME, - taskDefinition: 'vdf-job', - capacityProviderStrategy: [ - { - capacityProvider: 'FARGATE_SPOT', - weight: 1, - }, - ], - networkConfiguration: { - awsvpcConfiguration: { - subnets: networkConfig.subnets, - securityGroups: networkConfig.securityGroups, - assignPublicIp: 'ENABLED', - }, - }, - count: 1, - overrides: { - containerOverrides: [ - { - name: 'vdf_job_container', // Must match the container name in the task definition - command: ['sh', '-c', `python3 main.py ${random_per_vdf}`], - }, - ], - }, - }).promise(); - - const taskArn = result.tasks?.[0]?.taskArn; - return taskArn || null; -} - diff --git a/orchestrator/src/helperFunctions.ts b/orchestrator/src/helperFunctions.ts new file mode 100644 index 0000000..b1f26c8 --- /dev/null +++ b/orchestrator/src/helperFunctions.ts @@ -0,0 +1,396 @@ +import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, Logger, LogLevel, RandomClient } from "ao-process-clients"; +import { Client } from "pg"; +import { COMPLETION_RETENTION_PERIOD_MS, DRYRUNTIMEOUT } from "./app"; + + +let randomClientInstance: RandomClient | null = null; +let lastInitTime: number = 0; +const REINIT_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds + +const AO_CONFIG = { + MU_URL: "https://ur-mu.randao.net", + CU_URL: "https://ur-cu.randao.net", + GATEWAY_URL: "https://arweave.net", +}; +// Optional: Auto-reinitialize on a timer +setInterval(() => { + randomClientInstance = null; +}, REINIT_INTERVAL); + +export async function getRandomClient(): Promise { + const currentTime = Date.now(); + Logger.setLogLevel(LogLevel.DEBUG) + if (!randomClientInstance || (currentTime - lastInitTime) > REINIT_INTERVAL) { + randomClientInstance = ((await RandomClient.defaultBuilder()) + .withAOConfig(AO_CONFIG)) + .withWallet(JSON.parse(process.env.WALLET_JSON!)) + .build(); + lastInitTime = currentTime; + } + + return randomClientInstance; +} + + + + + + + + +// Step 2: Process Challenge Requests (Database selection & assigning is atomic) +export async function processChallengeRequests( + client: Client, + activeChallengeRequests: { request_ids: string[] } | undefined, + parentLogId: string +): Promise { + console.log(`${parentLogId} Step 2: Processing challenge requests.`); + + if (!activeChallengeRequests || activeChallengeRequests.request_ids.length === 0) { + console.log(`${parentLogId} No Challenge Requests to process.`); + return; + } + + const requestIds = activeChallengeRequests.request_ids; + console.log(`${parentLogId} Processing up to ${requestIds.length} requests.`); + + try { + await client.query('BEGIN'); // Start transaction + + console.log(`${parentLogId} Fetching existing request mappings.`); + + // Fetch already assigned request_id -> dbId mappings + const existingMappingsRes = await client.query( + `SELECT request_id FROM time_lock_puzzles + WHERE request_id = ANY($1) + FOR UPDATE SKIP LOCKED`, + [requestIds] + ); + + const existingRequestIds = new Set(existingMappingsRes.rows.map(row => row.request_id)); + console.log(`${parentLogId} Found ${existingRequestIds.size} already mapped requests.`); + + // Find only the unmapped requests (requestIds not in existingRequestIds) + const unmappedRequestIds = requestIds.filter(requestId => !existingRequestIds.has(requestId)); + console.log(`${parentLogId} Unmapped requests: ${unmappedRequestIds.length}`); + + let mappedEntries: { requestId: string, dbId: number }[] = []; + + if (unmappedRequestIds.length > 0) { + console.log(`${parentLogId} Fetching available DB entries.`); + const dbRes = await client.query( + `SELECT id FROM time_lock_puzzles + WHERE request_id IS NULL + ORDER BY id ASC + LIMIT $1 + FOR UPDATE SKIP LOCKED`, + [unmappedRequestIds.length] + ); + + const availableDbEntries = dbRes.rows.map(row => row.id); + console.log(`${parentLogId} Found ${availableDbEntries.length} available DB entries.`); + + if (availableDbEntries.length > 0) { + const numMappings = Math.min(unmappedRequestIds.length, availableDbEntries.length); + + for (let i = 0; i < numMappings; i++) { + await client.query( + `UPDATE time_lock_puzzles + SET request_id = $1 + WHERE id = $2`, + [unmappedRequestIds[i], availableDbEntries[i]] + ); + mappedEntries.push({ requestId: unmappedRequestIds[i], dbId: availableDbEntries[i] }); + console.log(`${parentLogId} Assigned Request ID ${unmappedRequestIds[i]} to DB Entry ${availableDbEntries[i]}.`); + } + } else { + console.log(`${parentLogId} No available DB entries for unmapped requests.`); + } + } + + // Collect all request IDs (previously mapped + newly mapped) + const allRequestIds = [...existingRequestIds, ...mappedEntries.map(entry => entry.requestId)]; + + if (allRequestIds.length === 0) { + console.log(`${parentLogId} No requests to process. Committing transaction.`); + await client.query('COMMIT'); + return; + } + + await client.query('COMMIT'); // Commit all updates at once + console.log(`${parentLogId} Committed all changes. Now fulfilling challenges.`); + + // Call fulfillRandomChallenge for all request IDs + await Promise.all( + allRequestIds.map(requestId => + fulfillRandomChallenge(client, requestId, parentLogId) + .catch(error => console.error(`${parentLogId} Error fulfilling challenge for Request ID ${requestId}:`, error)) + ) + ); + + console.log(`${parentLogId} All challenges fulfilled`); + } catch (error:any) { + console.error(`${parentLogId} Error in processChallengeRequests:`, error); + await client.query('ROLLBACK'); // Rollback on failure + + console.error(`SQL State: ${error.code}, Message: ${error.message}`); + } +} +// Step 3: Process Output Requests (unchanged but with logging) +export async function processOutputRequests( + client: Client, + activeOutputRequests: { request_ids: string[] } | undefined, + parentLogId: string +): Promise { + console.log(`${parentLogId} Step 3: Processing output requests.`); + + if (!activeOutputRequests || activeOutputRequests.request_ids.length === 0) { + console.log(`${parentLogId} No Output Requests to process.`); + return; + } + + const outputPromises = activeOutputRequests.request_ids.map(async (requestId) => { + console.log(`${parentLogId} Processing output request ID: ${requestId}`); + + // Run fulfillRandomOutput asynchronously (do not await) + fulfillRandomOutput(client, requestId, parentLogId) + .catch(error => console.error(`${parentLogId} Error fulfilling output:`, error)); + }); + + await Promise.all(outputPromises); + console.log(`${parentLogId} Step 3 completed.`); +} + +// Step 4: Remove fulfilled entries no longer in use (unchanged but with logging) +export async function cleanupFulfilledEntries( + client: Client, + openRequests: any, + parentLogId: string +): Promise { + console.log(`${parentLogId} Step 4: Checking for fulfilled entries no longer in use.`); + + const now = new Date(); + const cutoffTime = new Date(now.getTime() - COMPLETION_RETENTION_PERIOD_MS); + + try { + await client.query('BEGIN'); + + // Fetch all entries with a request_id + const result = await client.query(` + SELECT id, request_id, detected_completed + FROM time_lock_puzzles + WHERE request_id IS NOT NULL + `); + + let markForDeletion: string[] = []; + let markAsCompleted: string[] = []; + + for (const row of result.rows) { + const { id, request_id, detected_completed } = row; + + // Check if this request is still active in challenge or output + const isStillInChallenge = openRequests.activeChallengeRequests?.request_ids.includes(request_id); + const isStillInOutput = openRequests.activeOutputRequests?.request_ids.includes(request_id); + + if (!isStillInChallenge && !isStillInOutput) { + if (!detected_completed) { + // Mark it for deletion by setting detected_completed timestamp + markAsCompleted.push(id); + } else if (new Date(detected_completed) < cutoffTime) { + // If already marked and older than retention period, delete it + markForDeletion.push(id); + } + } + } + + // Mark entries as completed + if (markAsCompleted.length > 0) { + await client.query(` + UPDATE time_lock_puzzles + SET detected_completed = NOW() + WHERE id = ANY($1) + `, [markAsCompleted]); + console.log(`${parentLogId} Marked ${markAsCompleted.length} entries as completed.`); + } + + // Delete old completed entries //TODO make sure its cleaning up BOTH tables + if (markForDeletion.length > 0) { + await client.query(` + DELETE FROM rsa_keys + WHERE id IN ( + SELECT rsa_id FROM time_lock_puzzles WHERE id = ANY($1) + ); + `, [markForDeletion]); + + await client.query(` + DELETE FROM time_lock_puzzles + WHERE id = ANY($1); + `, [markForDeletion]); + + console.log(`${parentLogId} Deleted ${markForDeletion.length} old completed entries and corresponding RSA keys.`); + } + + await client.query('COMMIT'); + } catch (error) { + console.error(`${parentLogId} Error in cleanupFulfilledEntries:`, error); + await client.query('ROLLBACK'); + } + + console.log(`${parentLogId} Step 4 completed.`); +} + + + + + + + + + + + + + + + + + + + +export async function getProviderRequests(PROVIDER_ID: string, parentLogId: string): Promise { + + let openRequests: GetOpenRandomRequestsResponse; + + // Create a function to fetch open requests with a timeout + const fetchOpenRequests = async (): Promise => { + try { + const response = await (await getRandomClient()).getOpenRandomRequests(PROVIDER_ID); + return response; + } catch (error) { + console.error(`${parentLogId} Error fetching requests: ${error}`); + return { /* Return a default or empty response here */ } as GetOpenRandomRequestsResponse; + } + }; + + try { + openRequests = await Promise.race([ + fetchOpenRequests().catch(err => { + throw new Error(`${parentLogId} Fetch Error: ${err}`); + }), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout')), DRYRUNTIMEOUT) + ) + ]); + } catch (error) { + console.log(`${parentLogId} Step 1: ${error}`); + //randclient.setDryRunAsMessage(true); + console.log("Removed this as its spamming") + console.log("Switching dryrun off"); + openRequests = await fetchOpenRequests(); // Retry request + } + + if(openRequests.toString().includes("not found")){ + return false + } + console.log(`${parentLogId} Step 1: Open Requests: ${JSON.stringify(openRequests)}`); + console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${openRequests.activeChallengeRequests.request_ids.length}`); + console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${openRequests.activeOutputRequests.request_ids.length}`); + return openRequests; +} + +export function updateAvailableValuesAsync(currentCount: number) { + return (async () => { + try { + const randomClient = await getRandomClient(); + await randomClient.updateProviderAvailableValues(currentCount); + console.log(`Updated provider values to ${currentCount}`); + } catch (error) { + console.error("Failed to update provider values:", error); + } + })(); +} +export async function getProviderAvailableRandomValues(PROVIDER_ID: string): Promise { + try { + const randomClient = await getRandomClient(); + return await randomClient.getProviderAvailableValues(PROVIDER_ID); + } catch (error) { + console.error(`Error fetching available random values: ${error}`); + return {} as GetProviderAvailableValuesResponse; + } +} + +// Function to post VDF challenge (fetches dbId dynamically) +async function fulfillRandomChallenge(client: Client, requestId: string, parentLogId: string): Promise { + try { + // Fetch the necessary details from the database using requestId + const res = await client.query( + `SELECT id, modulus, x + FROM time_lock_puzzles + WHERE request_id = $1`, + [requestId] + ); + + if (!res.rowCount) { + console.error(`No entry found for Request ID: ${requestId}`); + return; + } + + const { id: dbId, modulus, x: input } = res.rows[0]; + + console.log(`${parentLogId} Fetched entry details - Request ID: ${requestId}, DB ID: ${dbId}, Modulus: ${modulus}, Input: ${input}`); + + console.log(`${parentLogId} Posting VDF challenge for Request ID: ${requestId}, DB ID: ${dbId}`); + await (await getRandomClient()).commit({ + requestId: requestId, + puzzle: { + input: input, + modulus: modulus + } + }); + console.log(`${parentLogId} Challenge posted for Request ID: ${requestId}. Waiting to post proof...`); + } catch (error) { + console.error(`${parentLogId} Error posting VDF challenge for Request ID: ${requestId}:`, error); + } +} +// Function to post VDF output and proof +async function fulfillRandomOutput(client: Client, requestId: string, parentLogId: string): Promise { + try { + // Fetch the output and proof from the database using the requestId + const res = await client.query( + `SELECT + tlp.id, + tlp.y AS output, + rk.p, + rk.q + FROM time_lock_puzzles tlp + JOIN rsa_keys rk ON tlp.rsa_id = rk.id + WHERE tlp.request_id = $1`, + [requestId] + ); + + if (!res.rowCount) { + console.error(`No entry found for request ID: ${requestId}`); + return; + } + // Map the response to structured variables +const { + id: dbId, + output, // Mapping 'y' to 'output' + p: rsaP, + q: rsaQ +} = res.rows[0]; + console.log(`${parentLogId} Fetched entry from database for output - ID: ${dbId}, requestID: ${requestId}, output: ${output}, rsaP: ${rsaP}, rsaQ ${rsaQ} `); + + console.log(`${parentLogId} Posting VDF output and proof for - ID: ${dbId}, request ID: ${requestId}`); + await (await getRandomClient()).reveal({ + requestId: requestId, + rsa_key: { + p: rsaP, + q: rsaQ + } + }) + console.log(`${parentLogId} Proof posted for request ID: ${requestId}`); + } catch (error) { + console.error(`${parentLogId} Error fulfilling random output for request ID: ${requestId}:`, error); + } +} \ No newline at end of file diff --git a/orchestrator/src/clear_outputs.tzs b/orchestrator/src/oldjunk/clear_outputs.tzs similarity index 100% rename from orchestrator/src/clear_outputs.tzs rename to orchestrator/src/oldjunk/clear_outputs.tzs diff --git a/orchestrator/src/stake.tzs b/orchestrator/src/oldjunk/stake.tzs similarity index 100% rename from orchestrator/src/stake.tzs rename to orchestrator/src/oldjunk/stake.tzs diff --git a/orchestrator/src/reset_db.ts b/orchestrator/src/reset_db.ts index ae62d50..8a58e79 100644 --- a/orchestrator/src/reset_db.ts +++ b/orchestrator/src/reset_db.ts @@ -1,5 +1,5 @@ -import { Client, QueryResult } from 'pg'; -import { dbConfig } from './db_config'; +import { Client } from 'pg'; +import { dbConfig } from './db_tools'; interface TableRow { tablename: string; diff --git a/orchestrator/tsconfig.json b/orchestrator/tsconfig.json index 786e06e..bb6eb3a 100644 --- a/orchestrator/tsconfig.json +++ b/orchestrator/tsconfig.json @@ -14,7 +14,7 @@ }, "include": [ "src/**/*.ts", - "src/db_config.ts", + "src/db_tools.ts", "src/clear_all_output_requests.ts", "src/reset_db.mjs", "src/clear_outputs.tzs" diff --git a/requester/src/app.ts b/requester/src/app.ts index 5837991..047de7f 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -1,15 +1,8 @@ import { - ProviderStakingClient, - getRandomClientAutoConfiguration, - IRandomClient, RandomClient, - RandomClientConfig, - StakingClient, - ProviderProfileClient, - RandomClientConfigBuilder, } from "ao-process-clients"; -const RETRY_DELAY_MS = 30000; // 30 seconds +const RETRY_DELAY_MS = 5000; // 5 seconds const PROVIDER_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes const PROVIDER_REQUEST_TIMEOUT = 60 * 1000; // 1 minute const CHANCE_TO_CALL_RANDOM = 1; diff --git a/terraform/.debug_task.tf b/terraform/.debug_task.tf deleted file mode 100644 index 492af95..0000000 --- a/terraform/.debug_task.tf +++ /dev/null @@ -1,36 +0,0 @@ -# debug_task.tf - -resource "aws_ecs_task_definition" "debug_task" { - family = "debug-task" - network_mode = "awsvpc" - requires_compatibilities = ["FARGATE"] - cpu = "512" - memory = "1024" - - container_definitions = jsonencode([ - { - name = "debug-container" - image = "amazonlinux" - essential = true - command = ["/bin/sh", "-c", "while true; do sleep 60; done"] - environment = [ - { name = "DB_HOST", value = "postgres" }, - { name = "DB_PORT", value = "5432" }, - { name = "DB_USER", value = "myuser" }, - { name = "DB_PASSWORD", value = "mypassword" }, - { name = "DB_NAME", value = "mydatabase" } - ], - logConfiguration = { - logDriver = "awslogs" - options = { - awslogs-group = "/ecs/debug-task" - awslogs-region = var.aws_region - awslogs-stream-prefix = "debug-container" - } - } - } - ]) - - execution_role_arn = aws_iam_role.execution_role.arn - task_role_arn = aws_iam_role.task_role.arn -} diff --git a/terraform/README.md b/terraform/README.md deleted file mode 100644 index 931ab97..0000000 --- a/terraform/README.md +++ /dev/null @@ -1,184 +0,0 @@ -# AWS Terraform Setup Guide - -This guide walks you through deploying a randomness provider using Terraform and AWS cloud services for optimal performance and reliability. - -## Prerequisites - -- An AWS account with appropriate permissions -- Basic familiarity with AWS services and Terraform -- Terraform installed on your local machine - -## Steps to Deploy - -1. **Install Terraform** - Follow the [official Terraform installation guide](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli). - -2. **Set Up IAM User and Policy** - 1. Go to AWS IAM and create a new policy called `RandAO-Provider-Admin` - 2. Select the JSON tab and paste this policy: - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ecs:CreateCluster", - "ecs:DeleteCluster", - "ecs:CreateService", - "ecs:DeleteService", - "ecs:UpdateService", - "ecs:RegisterTaskDefinition", - "ecs:DeregisterTaskDefinition", - "ecs:ListTaskDefinitions", - "ecs:DescribeTaskDefinition", - "ecs:PutClusterCapacityProviders", - "ecs:DescribeClusters" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:CreateRole", - "iam:DeleteRole", - "iam:GetRole", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy", - "iam:AttachRolePolicy", - "iam:DetachRolePolicy", - "iam:PassRole" - ], - "Resource": "arn:aws:iam::*:role/orchestrator-*" - }, - { - "Effect": "Allow", - "Action": [ - "secretsmanager:CreateSecret", - "secretsmanager:DeleteSecret", - "secretsmanager:GetSecretValue", - "secretsmanager:PutSecretValue", - "secretsmanager:UpdateSecret", - "secretsmanager:TagResource" - ], - "Resource": "arn:aws:secretsmanager:*:*:secret:/orchestrator/*" - }, - { - "Effect": "Allow", - "Action": [ - "rds:CreateDBInstance", - "rds:DeleteDBInstance", - "rds:ModifyDBInstance", - "rds:DescribeDBInstances", - "rds:CreateDBSubnetGroup", - "rds:DeleteDBSubnetGroup", - "rds:ModifyDBSubnetGroup", - "rds:AddTagsToResource" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:DeleteLogGroup", - "logs:PutRetentionPolicy" - ], - "Resource": "arn:aws:logs:*:*:log-group:*" - }, - { - "Effect": "Allow", - "Action": [ - "ec2:CreateSecurityGroup", - "ec2:DeleteSecurityGroup", - "ec2:AuthorizeSecurityGroupIngress", - "ec2:RevokeSecurityGroupIngress", - "ec2:CreateVpcEndpoint", - "ec2:DeleteVpcEndpoints", - "ec2:DescribeVpcEndpoints", - "ec2:DescribeSecurityGroups", - "ec2:DescribeNetworkInterfaces", - "ec2:CreateTags" - ], - "Resource": "*" - } - ] - } - ``` - 3. Save the policy and name it - 4. Go to Users and create a new user called `TerraformDeployer` - 5. Attach the `RandAO-Provider-Admin` policy directly - 6. Create an access key for the user (choose CLI) - 7. Save the access key ID and secret access key for the next step - -3. **Configure AWS Environment Variables** - Open a terminal and enter the following commands: - ```bash - export AWS_ACCESS_KEY_ID="your-access-key-id" - export AWS_SECRET_ACCESS_KEY="your-secret-access-key" - export AWS_REGION="your-region" # e.g., us-east-1 - ``` - -4. **Set Up Terraform Variables** - Navigate to the Terraform directory of the project: - ```bash - cp terraform.tfvars.example terraform.tfvars - ``` - Edit the `terraform.tfvars` file and fill in all required variables: - - **Required Variables:** - - `provider_id`: Your unique provider identifier - - `local_db_user`: Database username - - `local_db_password`: Database password - - `local_wallet_json`: Wallet JSON (either direct or via file path) - - **Optional Variables (with defaults):** - - `aws_region`: AWS region (default: us-east-1) - - `db_name`: Database name (default: orchestrator_db) - - `secrets_prefix`: Prefix for secrets (default: /orchestrator) - - The database configuration is highly suggested, and the secrets can be left at default values as they're just the names of the secrets. - -5. **Initialize and Apply Terraform Configuration** - Navigate to the Terraform directory of the project and run: - ```bash - terraform init - terraform apply - ``` - Type `yes` when prompted to confirm. - -6. **Verify Deployment** - Open the AWS console and navigate to the ECS console to view your running services. - -## How It Works - -This deployment creates the following AWS resources: - -1. **ECS Cluster**: Runs the orchestrator service and puzzle generator jobs -2. **RDS**: PostgreSQL database for storing time lock puzzles and provider state -3. **Secrets Manager**: Securely stores database credentials and wallet information -4. **IAM**: Roles and policies for ECS tasks and secrets access -5. **CloudWatch**: Log groups for monitoring your provider - -## Advantages of AWS Deployment - -- **Cost Efficiency at Scale**: Most cost-effective for dedicated providers -- **Maximum Reliability**: AWS services offer SLAs for high availability -- **Automatic Scaling**: Resources scale based on demand -- **Managed Services**: AWS handles infrastructure maintenance -- **High Performance**: Optimized for speed and reliability - -## Security Notes - -- The `terraform.tfvars` file contains sensitive information and should never be committed to version control -- Add `terraform.tfvars` and `wallet.json` to your `.gitignore` -- In production, sensitive values are stored in AWS Secrets Manager -- Local development variables are only used for initial setup and testing - -## Staking Process - -After deployment, please open the AWS console and share the logs with Ethan to receive the tokens needed for staking. Follow the main documentation for the staking process. - ---- - -[Return to Main Documentation](../README.md) diff --git a/terraform/ecs.tf b/terraform/ecs.tf deleted file mode 100644 index ca10463..0000000 --- a/terraform/ecs.tf +++ /dev/null @@ -1,315 +0,0 @@ -# ECS Cluster and Services -resource "aws_ecs_cluster" "fargate_cluster" { - name = "orchestrator-cluster" -} - -resource "aws_ecs_cluster_capacity_providers" "fargate_providers" { - cluster_name = aws_ecs_cluster.fargate_cluster.name - - capacity_providers = ["FARGATE", "FARGATE_SPOT"] - - default_capacity_provider_strategy { - base = 1 - weight = 100 - capacity_provider = "FARGATE_SPOT" - } -} - -# Orchestrator Service -resource "aws_ecs_service" "orchestrator_service" { - name = "orchestrator-service" - cluster = aws_ecs_cluster.fargate_cluster.id - task_definition = aws_ecs_task_definition.orchestrator_service.arn - desired_count = 1 - launch_type = "FARGATE" - - network_configuration { - subnets = local.subnet_ids - security_groups = local.ecs_security_groups - assign_public_ip = true - } -} - -# VDF Job Service -resource "aws_ecs_service" "vdf_job_service" { - name = "vdf-job-service" - cluster = aws_ecs_cluster.fargate_cluster.id - task_definition = aws_ecs_task_definition.vdf_job.arn - desired_count = 0 - - capacity_provider_strategy { - capacity_provider = "FARGATE_SPOT" - weight = 1 - } - - network_configuration { - subnets = local.subnet_ids - security_groups = local.ecs_security_groups - assign_public_ip = true - } -} - -# Task Definitions -resource "aws_ecs_task_definition" "orchestrator_service" { - family = "orchestrator-service" - network_mode = "awsvpc" - requires_compatibilities = ["FARGATE"] - cpu = "512" - memory = "1024" - execution_role_arn = aws_iam_role.ecs_execution_role.arn - task_role_arn = aws_iam_role.ecs_task_role.arn - - container_definitions = jsonencode([ - { - name = "orchestrator" - image = "randao/orchestrator:v0.4.55" - environment = [ - { - name = "ENVIRONMENT" - value = "cloud" - }, - { - name = "PROVIDER_ID" - value = var.provider_id - }, - { - name = "DB_HOST" - value = split(":", aws_db_instance.orchestrator.endpoint)[0] - }, - { - name = "DB_PORT" - value = "5432" - }, - { - name = "DB_NAME" - value = var.db_name - }, - { - name = "MAX_RETRIES" - value = "10" - }, - { - name = "RETRY_DELAY_MS" - value = "10000" - }, - { - name = "PGCONNECT_TIMEOUT" - value = "30" - }, - { - name = "AWS_REGION" - value = var.aws_region - }, - { - name = "ECS_CLUSTER_NAME" - value = aws_ecs_cluster.fargate_cluster.name - } - ] - secrets = [ - { - name = "DB_USER" - valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:username::" - }, - { - name = "DB_PASSWORD" - valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:password::" - }, - { - name = "WALLET_JSON" - valueFrom = aws_secretsmanager_secret.wallet.arn - } - ] - logConfiguration = { - logDriver = "awslogs" - options = { - awslogs-group = "/ecs/orchestrator" - awslogs-region = var.aws_region - awslogs-stream-prefix = "orchestrator" - } - } - } - ]) -} - -# VDF Task Definition -resource "aws_ecs_task_definition" "vdf_job" { - family = "vdf-job" - network_mode = "awsvpc" - requires_compatibilities = ["FARGATE"] - cpu = "2048" # 2 vCPU - memory = "4096" # 4GB - execution_role_arn = aws_iam_role.ecs_execution_role.arn - task_role_arn = aws_iam_role.ecs_task_role.arn - - container_definitions = jsonencode([ - { - name = "vdf_job_container" - image = "randao/puzzle-gen:v0.1.1" - command = ["python", "main.py"], - environment = [ - { - name = "DATABASE_TYPE" - value = "postgresql" - }, - { - name = "DATABASE_HOST" - value = split(":", aws_db_instance.orchestrator.endpoint)[0] - }, - { - name = "DATABASE_PORT" - value = "5432" - }, - { - name = "DATABASE_NAME" - value = var.db_name - } - ] - secrets = [ - { - name = "DATABASE_USER" - valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:username::" - }, - { - name = "DATABASE_PASSWORD" - valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:password::" - } - ] - logConfiguration = { - logDriver = "awslogs" - options = { - awslogs-group = "/ecs/vdf-job" - awslogs-region = var.aws_region - awslogs-stream-prefix = "vdf" - } - } - } - ]) -} - -# IAM Roles -resource "aws_iam_role" "ecs_execution_role" { - name = "orchestrator-ecs-execution-role" - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Effect = "Allow" - Principal = { - Service = "ecs-tasks.amazonaws.com" - } - } - ] - }) -} - -resource "aws_iam_role" "ecs_task_role" { - name = "orchestrator-ecs-task-role" - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Effect = "Allow" - Principal = { - Service = "ecs-tasks.amazonaws.com" - } - } - ] - }) -} - -# Attach AWS managed policy for ECS task execution -resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" { - role = aws_iam_role.ecs_execution_role.name - policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" -} - -# Add ECS permissions to task role -resource "aws_iam_role_policy" "ecs_task_permissions" { - name = "orchestrator-ecs-permissions" - role = aws_iam_role.ecs_task_role.id - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "ecs:RunTask", - "ecs:StopTask", - "ecs:DescribeTasks", - "ecs:ListTasks", - "ecs:DescribeTaskDefinition", - "ecs:ListTaskDefinitions", - "ecs:DescribeServices", - "ecs:ListServices", - "ecs:DescribeClusters", - "ecs:ListClusters", - "iam:PassRole" - ] - Resource = "*" - } - ] - }) -} - -# Add networking permissions to task role -resource "aws_iam_role_policy" "ecs_task_networking_policy" { - name = "orchestrator-networking-policy" - role = aws_iam_role.ecs_task_role.id - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "ec2:DescribeNetworkInterfaces", - "ec2:CreateNetworkInterface", - "ec2:DeleteNetworkInterface", - "ec2:DescribeInstances", - "ec2:AttachNetworkInterface" - ] - Resource = ["*"] - } - ] - }) -} - -# Add Secrets Manager access to execution role -resource "aws_iam_role_policy" "ecs_execution_secrets_policy" { - name = "orchestrator-secrets-access" - role = aws_iam_role.ecs_execution_role.id - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret", - "kms:Decrypt" - ] - Resource = [ - aws_secretsmanager_secret.db_credentials.arn, - aws_secretsmanager_secret.wallet.arn - ] - } - ] - }) -} - -# CloudWatch Log Groups -resource "aws_cloudwatch_log_group" "orchestrator" { - name = "/ecs/orchestrator" - retention_in_days = 30 -} - -resource "aws_cloudwatch_log_group" "vdf" { - name = "/ecs/vdf-job" - retention_in_days = 30 -} diff --git a/terraform/iam.json b/terraform/iam.json deleted file mode 100644 index 1150854..0000000 --- a/terraform/iam.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ecs:CreateCluster", - "ecs:DeleteCluster", - "ecs:CreateService", - "ecs:DeleteService", - "ecs:UpdateService", - "ecs:RegisterTaskDefinition", - "ecs:DeregisterTaskDefinition", - "ecs:ListTaskDefinitions", - "ecs:DescribeTaskDefinition", - "ecs:PutClusterCapacityProviders", - "ecs:DescribeClusters" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:CreateRole", - "iam:DeleteRole", - "iam:GetRole", - "iam:PutRolePolicy", - "iam:DeleteRolePolicy", - "iam:AttachRolePolicy", - "iam:DetachRolePolicy", - "iam:PassRole" - ], - "Resource": "arn:aws:iam::*:role/orchestrator-*" - }, - { - "Effect": "Allow", - "Action": [ - "secretsmanager:CreateSecret", - "secretsmanager:DeleteSecret", - "secretsmanager:GetSecretValue", - "secretsmanager:PutSecretValue", - "secretsmanager:UpdateSecret", - "secretsmanager:TagResource" - ], - "Resource": "arn:aws:secretsmanager:*:*:secret:/orchestrator/*" - }, - { - "Effect": "Allow", - "Action": [ - "rds:CreateDBInstance", - "rds:DeleteDBInstance", - "rds:ModifyDBInstance", - "rds:DescribeDBInstances", - "rds:CreateDBSubnetGroup", - "rds:DeleteDBSubnetGroup", - "rds:ModifyDBSubnetGroup", - "rds:AddTagsToResource" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:DeleteLogGroup", - "logs:PutRetentionPolicy" - ], - "Resource": "arn:aws:logs:*:*:log-group:*" - }, - { - "Effect": "Allow", - "Action": [ - "ec2:CreateSecurityGroup", - "ec2:DeleteSecurityGroup", - "ec2:AuthorizeSecurityGroupIngress", - "ec2:RevokeSecurityGroupIngress", - "ec2:CreateVpcEndpoint", - "ec2:DeleteVpcEndpoints", - "ec2:DescribeVpcEndpoints", - "ec2:DescribeSecurityGroups", - "ec2:DescribeNetworkInterfaces", - "ec2:CreateTags" - ], - "Resource": "*" - } - ] -} diff --git a/terraform/iam_roles.tf b/terraform/iam_roles.tf deleted file mode 100644 index 8569ed1..0000000 --- a/terraform/iam_roles.tf +++ /dev/null @@ -1,83 +0,0 @@ -# iam_roles.tf - -data "aws_caller_identity" "current" {} - -resource "aws_iam_role" "task_role" { - name = "orchestrator-task-role" - assume_role_policy = data.aws_iam_policy_document.task_role_policy.json -} - -resource "aws_iam_role" "execution_role" { - name = "orchestrator-execution-role" - assume_role_policy = data.aws_iam_policy_document.execution_role_policy.json -} - -data "aws_iam_policy_document" "task_role_policy" { - statement { - actions = ["sts:AssumeRole"] - principals { - type = "Service" - identifiers = ["ecs-tasks.amazonaws.com"] - } - } -} - -data "aws_iam_policy_document" "execution_role_policy" { - statement { - actions = ["sts:AssumeRole"] - principals { - type = "Service" - identifiers = ["ecs-tasks.amazonaws.com"] - } - } -} - -# Attach necessary policies to orchestrator-task-role -resource "aws_iam_role_policy" "orchestrator_task_policy" { - name = "orchestrator-task-policy" - role = aws_iam_role.task_role.id - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "ecs:RunTask", - "ecs:StopTask", - "ecs:DescribeTasks", - "ecs:ListTasks", - "ecs:DescribeTaskDefinition", - "ecs:ListTaskDefinitions", - "ecs:DescribeServices", - "ecs:ListServices", - "ecs:DescribeClusters", - "ecs:ListClusters" - ] - Resource = "*" - }, - { - Effect = "Allow" - Action = [ - "iam:PassRole" - ] - Resource = [ - aws_iam_role.execution_role.arn, - aws_iam_role.task_role.arn - ] - } - ] - }) -} - - -# Attach policies to the execution role -resource "aws_iam_role_policy_attachment" "ecs_task_execution" { - role = aws_iam_role.execution_role.name - policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" -} - -resource "aws_iam_role_policy_attachment" "ecs_logs_policy" { - role = aws_iam_role.execution_role.name - policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" -} diff --git a/terraform/locals.tf b/terraform/locals.tf deleted file mode 100644 index f83cd52..0000000 --- a/terraform/locals.tf +++ /dev/null @@ -1,23 +0,0 @@ -# Local variables - -locals { - # Default VPC subnets - you may want to customize these for your environment - subnet_ids = data.aws_subnets.default.ids -} - -# Data sources for networking -data "aws_vpc" "default" { - default = true -} - -data "aws_subnets" "default" { - filter { - name = "vpc-id" - values = [data.aws_vpc.default.id] - } -} - -data "aws_security_group" "default" { - vpc_id = data.aws_vpc.default.id - name = "default" -} diff --git a/terraform/logging.tf b/terraform/logging.tf deleted file mode 100644 index 5ee8731..0000000 --- a/terraform/logging.tf +++ /dev/null @@ -1,6 +0,0 @@ -# logging.tf - -resource "aws_cloudwatch_log_group" "ecs_orchestrator_service" { - name = "ecs-orchestrator-service" - retention_in_days = 30 # Adjust retention as needed -} diff --git a/terraform/outputs.tf b/terraform/outputs.tf deleted file mode 100644 index aedeac0..0000000 --- a/terraform/outputs.tf +++ /dev/null @@ -1,18 +0,0 @@ -# outputs.tf - -# Outputs for resource information - -output "database_endpoint" { - description = "The connection endpoint for the RDS instance" - value = aws_db_instance.orchestrator.endpoint -} - -output "orchestrator_service_name" { - description = "The name of the ECS service running the orchestrator" - value = aws_ecs_service.orchestrator_service.name -} - -output "cloudwatch_log_group" { - description = "The CloudWatch log group for the orchestrator service" - value = aws_cloudwatch_log_group.orchestrator.name -} diff --git a/terraform/package.json b/terraform/package.json deleted file mode 100644 index b4f6756..0000000 --- a/terraform/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dependencies": { - "ao-process-clients": "^2.3.17" - }, - "devDependencies": { - "@types/node": "^22.9.1" - } -} diff --git a/terraform/providers.tf b/terraform/providers.tf deleted file mode 100644 index c40a6fd..0000000 --- a/terraform/providers.tf +++ /dev/null @@ -1,16 +0,0 @@ -# Configure AWS Provider -provider "aws" { - region = var.aws_region -} - -# Configure required providers -terraform { - required_providers { - aws = { - source = "hashicorp/aws" - version = "~> 5.0" - } - } - - required_version = ">= 1.2.0" -} diff --git a/terraform/rds.tf b/terraform/rds.tf deleted file mode 100644 index 46888f3..0000000 --- a/terraform/rds.tf +++ /dev/null @@ -1,66 +0,0 @@ -# RDS PostgreSQL Instance - -# Security group for RDS -resource "aws_security_group" "rds" { - name = "orchestrator-rds-sg" - description = "Security group for RDS instance" - vpc_id = data.aws_vpc.default.id - - ingress { - from_port = 5432 - to_port = 5432 - protocol = "tcp" - security_groups = local.ecs_security_groups - description = "Allow PostgreSQL access from ECS tasks" - } - - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - description = "Allow all outbound traffic" - } - - tags = { - Name = "orchestrator-rds-sg" - } -} - -# RDS subnet group -resource "aws_db_subnet_group" "orchestrator" { - name = "orchestrator-subnet-group" - subnet_ids = local.subnet_ids - - tags = { - Name = "Orchestrator DB subnet group" - } -} - -# RDS PostgreSQL Instance -resource "aws_db_instance" "orchestrator" { - identifier = "orchestrator-db" - engine = "postgres" - engine_version = "13" - instance_class = "db.t3.micro" - allocated_storage = 20 - storage_type = "gp2" - - db_name = var.db_name - username = var.local_db_user - password = var.local_db_password - - vpc_security_group_ids = [aws_security_group.rds.id] - db_subnet_group_name = aws_db_subnet_group.orchestrator.name - - skip_final_snapshot = true - publicly_accessible = false - - backup_retention_period = 7 - backup_window = "03:00-04:00" - maintenance_window = "Mon:04:00-Mon:05:00" - - tags = { - Name = "orchestrator-db" - } -} diff --git a/terraform/secrets.tf b/terraform/secrets.tf deleted file mode 100644 index 7a89ef4..0000000 --- a/terraform/secrets.tf +++ /dev/null @@ -1,62 +0,0 @@ -# AWS Secrets Manager resources - -# Database credentials secret -resource "aws_secretsmanager_secret" "db_credentials" { - name = "${var.secrets_prefix}/${var.db_credentials_secret_name}" - description = "Database credentials for the orchestrator service" - - lifecycle { - ignore_changes = [name] - } -} - -resource "aws_secretsmanager_secret_version" "db_credentials" { - secret_id = aws_secretsmanager_secret.db_credentials.id - secret_string = jsonencode({ - username = var.local_db_user - password = var.local_db_password - }) -} - -# Wallet secret -resource "aws_secretsmanager_secret" "wallet" { - name = "${var.secrets_prefix}/${var.wallet_secret_name}" - description = "Wallet JSON for the orchestrator service" - - lifecycle { - ignore_changes = [name] - } -} - -resource "aws_secretsmanager_secret_version" "wallet" { - secret_id = aws_secretsmanager_secret.wallet.id - secret_string = var.local_wallet_json -} - - -# IAM policy for ECS tasks to access secrets -data "aws_iam_policy_document" "secrets_access" { - statement { - effect = "Allow" - actions = [ - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret" - ] - resources = [ - aws_secretsmanager_secret.db_credentials.arn, - aws_secretsmanager_secret.wallet.arn - ] - } -} - -resource "aws_iam_policy" "secrets_access" { - name = "orchestrator-secrets-access" - description = "Allow access to orchestrator secrets" - policy = data.aws_iam_policy_document.secrets_access.json -} - -# Attach secrets policy to ECS task role -resource "aws_iam_role_policy_attachment" "ecs_task_secrets" { - role = aws_iam_role.ecs_task_role.name - policy_arn = aws_iam_policy.secrets_access.arn -} diff --git a/terraform/security.tf b/terraform/security.tf deleted file mode 100644 index 7a0545d..0000000 --- a/terraform/security.tf +++ /dev/null @@ -1,33 +0,0 @@ -# Security Groups - -# Security group for ECS tasks -resource "aws_security_group" "ecs_tasks" { - name = "orchestrator-ecs-tasks-sg" - description = "Security group for ECS tasks" - vpc_id = data.aws_vpc.default.id - - ingress { - from_port = 0 - to_port = 0 - protocol = "-1" - self = true - description = "Allow all traffic between tasks" - } - - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - description = "Allow all outbound traffic" - } - - tags = { - Name = "orchestrator-ecs-tasks-sg" - } -} - -# Update references to use the new security group -locals { - ecs_security_groups = [aws_security_group.ecs_tasks.id] -} diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example deleted file mode 100644 index fcfa02b..0000000 --- a/terraform/terraform.tfvars.example +++ /dev/null @@ -1,31 +0,0 @@ -# AWS Region -aws_region = "us-east-1" - -# Provider Configuration -provider_id = "your-provider-id" # Set this to your unique provider identifier - -# Database Configuration (for local development/testing) -local_db_user = "myuser" # Change this -local_db_password = "mypassword" # Change this -db_name = "orchestrator_db" - -# Wallet Configuration -# Copy the contents of your wallet.json file and paste it here -local_wallet_json = < Date: Mon, 14 Apr 2025 14:50:14 -0400 Subject: [PATCH 46/51] added new cu code --- docker-compose/docker-compose.yml | 2 +- orchestrator/docs/development.md | 2 +- orchestrator/package.json | 2 +- orchestrator/src/app.ts | 48 ++----- orchestrator/src/containerManagment.ts | 65 --------- orchestrator/src/helperFunctions.ts | 190 +++++++++++++++++-------- requester/package.json | 3 +- requester/src/app.ts | 22 +-- requester/src/extra.ts | 60 ++++++++ 9 files changed, 225 insertions(+), 169 deletions(-) create mode 100644 requester/src/extra.ts diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 4fff7ae..b54467e 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.4.55 + image: randao/orchestrator:v0.4.60 depends_on: postgres: condition: service_healthy diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index d7f3b1b..fcff170 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.4.55 # You can change this value to any version you want +export VERSION=v0.4.60 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/package.json b/orchestrator/package.json index 051e67b..11369e6 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "^6.0.3", + "ao-process-clients": "^6.0.16", "ao-vrf": "file:", "arweave": "^1.15.5", "aws-sdk": "^2.1692.0", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index f6c9c44..794fef9 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -1,10 +1,9 @@ -import { Client } from 'pg'; import Docker from 'dockerode'; import AWS from 'aws-sdk'; -import { connectWithRetry, dbConfig, setupDatabase } from './db_tools.js'; +import { connectWithRetry, setupDatabase } from './db_tools.js'; import Arweave from 'arweave'; -import { cleanupFulfilledEntries, getProviderRequests, getRandomClient, processChallengeRequests, processOutputRequests } from './helperFunctions.js'; -import { checkAndFetchIfNeeded, monitorDockerContainers } from './containerManagment.js'; +import { checkAndFetchIfNeeded, cleanupFulfilledEntries, getProviderRequests, processChallengeRequests, processOutputRequests, shutdown } from './helperFunctions.js'; +import {monitorDockerContainers } from './containerManagment.js'; @@ -16,7 +15,7 @@ export const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.1'; export const DOCKER_MONITORING_TIME= 30000; -export const POLLING_INTERVAL_MS = 30000; //30 seconds +export const POLLING_INTERVAL_MS = 500; //0.5 seconds export const DATABASE_CHECK_TIME = 60000; //60 seconds export const MINIMUM_ENTRIES = 1000; export const DRYRUNTIMEOUT = 30000; // 30 seconds @@ -50,20 +49,6 @@ function resetStepTracking() { }; } - -async function shutdown() { - try { - const randomClient = await getRandomClient(); - let message = await randomClient.updateProviderAvailableValues(0); - console.log(message); - console.log(`Updated provider values to 0`); - } catch (error) { - console.error("Failed to update provider values:", error); - } -} - - - function getLogId(): string { const randomId = Math.floor(10000 + Math.random() * 90000); // 5-digit random number const timestamp = new Date().toLocaleTimeString("en-US", { hour12: false }); // HH:MM:SS format @@ -95,10 +80,6 @@ async function polling(client: any) { const s1 = Date.now(); console.log(`${logId} Step 1 started.`); const openRequests = await getProviderRequests(PROVIDER_ID, logId); - if(openRequests == false){ - console.log("Provider is set up and ready. Please stake to join network at https://providers_randao.ar.io") - return - } stepTracking.step1 = { completed: true, timeTaken: Date.now() - s1 }; console.log(`${logId} Step 1: Open requests fetched. Time taken: ${stepTracking.step1.timeTaken}ms`); @@ -123,6 +104,7 @@ async function polling(client: any) { console.log(`${logId} Step 4 started.`); //TODO enable this again later await cleanupFulfilledEntries(client, openRequests, logId); + await checkAndFetchIfNeeded(client) stepTracking.step4 = { completed: true, timeTaken: Date.now() - s4 }; console.log(`${logId} Step 4 completed. Time taken: ${stepTracking.step4.timeTaken}ms`); })(), @@ -150,16 +132,16 @@ async function run(): Promise { PROVIDER_ID = address }); - setInterval(async () => { - const res = await client.query('SELECT COUNT(*) as count FROM time_lock_puzzles'); - console.log(`Periodic log - Current database size: ${res.rows[0].count}`); - // Check and fetch entries for the database if needed - console.log("Step 0: Checking and fetching database entries if below threshold."); - checkAndFetchIfNeeded(client, PROVIDER_ID).catch((error) => { - console.error("Error in checkAndFetchIfNeeded:", error); - }); - - }, DATABASE_CHECK_TIME); + // setInterval(async () => { + // const res = await client.query('SELECT COUNT(*) as count FROM time_lock_puzzles'); + // console.log(`Periodic log - Current database size: ${res.rows[0].count}`); + // // Check and fetch entries for the database if needed + // console.log("Step 0: Checking and fetching database entries if below threshold."); + // checkAndFetchIfNeeded(client, PROVIDER_ID).catch((error) => { + // console.error("Error in checkAndFetchIfNeeded:", error); + // }); + + // }, DATABASE_CHECK_TIME); setInterval(async () => { await monitorDockerContainers(); diff --git a/orchestrator/src/containerManagment.ts b/orchestrator/src/containerManagment.ts index 438a149..026a035 100644 --- a/orchestrator/src/containerManagment.ts +++ b/orchestrator/src/containerManagment.ts @@ -1,16 +1,11 @@ import { docker, DOCKER_NETWORK, ecs, ENVIRONMENT, MINIMUM_ENTRIES, TIME_PUZZLE_JOB_IMAGE, UNCHAIN_VS_OFFCHAIN_MAX_DIF } from "./app"; import AWS from 'aws-sdk'; import { dbConfig } from "./db_tools"; -import { getProviderAvailableRandomValues, updateAvailableValuesAsync } from "./helperFunctions"; -import { Client } from "pg"; - export interface NetworkConfig { subnets: string[]; securityGroups: string[]; } - -let ongoingRequest = false; let spotInterruptions = 0; // Global variables to track polling status let pulledDockerimage = false; @@ -168,8 +163,6 @@ export async function getMoreRandom(currentCount: number) { const entriesNeeded = MINIMUM_ENTRIES - currentCount; console.log(`Less than ${MINIMUM_ENTRIES} entries found. Fetching ${entriesNeeded} more entries...`); - ongoingRequest = true; - if (ongoingContainers.size > 0) { console.log("A puzzle-gen container is already running. Skipping new container launch."); return null; @@ -262,61 +255,3 @@ export async function monitorDockerContainers(): Promise { function isDockerError(error: unknown): error is { statusCode: number } { return typeof error === 'object' && error !== null && 'statusCode' in error && typeof (error as any).statusCode === 'number'; } - -// Function to check and fetch database entries as needed -export async function checkAndFetchIfNeeded(client: Client, PROVIDER_ID:string) { - try { - //Check if provider has been given a special signal - const on_chain_avalible_random = await getProviderAvailableRandomValues(PROVIDER_ID); - // Query current count of usable DB entries - const res = await client.query( - 'SELECT COUNT(*) AS count FROM time_lock_puzzles WHERE request_id IS NULL' - ); - const currentCount = parseInt(res.rows[0].count, 10); - console.log("Total usable DB entries: " + currentCount); - - switch (on_chain_avalible_random.availibleRandomValues) { - case -1: - console.log("Value is -1"); - console.log("Provider has been shut down by USER..."); - console.log("Go to the provider dashboard to turn back on"); - //TODO prepare for shutdown - //TODO the async causes it to ovewrite itself - break; - case -2: - console.log("Value is -2"); - console.log("Provider has been shut down by PROCESS..."); - console.log("This is due to One of the following: "); - console.log("Failing to provide random fast enough (Provider is not responding to random requests and considered unhealthy NO SLASH)"); - console.log("Failing to provide the proof for an outstanding random request within the time. (Provider was likely turned off mid random request SMALL SLASH)" ); - console.log("Failing to provide the correct proof for your original random (Provider was detected as malicious for tampering with the random LARGE SLASH)"); - console.log("Go to the provider dashboard to turn back on"); - break; - case -3: - console.log("Value is -3"); - console.log("Provider has been shut down by PROCESS..."); - console.log("This was likely done as a test or to get the maintainers attention. Contact team if you see this and are not sure why"); - console.log("Go to the provider dashboard to turn back on"); - break; - default: - console.log("Value is not -1, -2, or -3"); - console.log("Provider is up and working"); - console.log("Onchain Value is "+ on_chain_avalible_random.availibleRandomValues) - console.log("Local Value is "+ currentCount) - if (Math.abs(on_chain_avalible_random.availibleRandomValues - currentCount) > UNCHAIN_VS_OFFCHAIN_MAX_DIF) { - console.log(`Updating available random values from ${on_chain_avalible_random.availibleRandomValues} to ${currentCount}`); - updateAvailableValuesAsync(currentCount); - } - } - if (ongoingRequest) return; // Prevent redundant operations - - // Check if more entries are needed - if (currentCount >= MINIMUM_ENTRIES) return; - getMoreRandom(currentCount) - - } catch (error) { - console.error('Error during check and fetch:', error); - } finally { - ongoingRequest = false; // Allow future operations - } -} \ No newline at end of file diff --git a/orchestrator/src/helperFunctions.ts b/orchestrator/src/helperFunctions.ts index b1f26c8..7ff242d 100644 --- a/orchestrator/src/helperFunctions.ts +++ b/orchestrator/src/helperFunctions.ts @@ -1,17 +1,19 @@ -import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, Logger, LogLevel, RandomClient } from "ao-process-clients"; +import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, Logger, LogLevel, RandomClient, RequestList } from "ao-process-clients"; import { Client } from "pg"; -import { COMPLETION_RETENTION_PERIOD_MS, DRYRUNTIMEOUT } from "./app"; +import { COMPLETION_RETENTION_PERIOD_MS, MINIMUM_ENTRIES, UNCHAIN_VS_OFFCHAIN_MAX_DIF } from "./app"; +import { getMoreRandom } from "./containerManagment"; let randomClientInstance: RandomClient | null = null; let lastInitTime: number = 0; const REINIT_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds - -const AO_CONFIG = { - MU_URL: "https://ur-mu.randao.net", - CU_URL: "https://ur-cu.randao.net", - GATEWAY_URL: "https://arweave.net", -}; +let current_onchain_random = - 10 +let ongoingRequest = false; +// const AO_CONFIG = { +// MU_URL: "https://ur-mu.randao.net", +// CU_URL: "https://ur-cu.randao.net", +// GATEWAY_URL: "https://arweave.net", +// }; // Optional: Auto-reinitialize on a timer setInterval(() => { randomClientInstance = null; @@ -21,8 +23,8 @@ export async function getRandomClient(): Promise { const currentTime = Date.now(); Logger.setLogLevel(LogLevel.DEBUG) if (!randomClientInstance || (currentTime - lastInitTime) > REINIT_INTERVAL) { - randomClientInstance = ((await RandomClient.defaultBuilder()) - .withAOConfig(AO_CONFIG)) + randomClientInstance = ((await RandomClient.defaultBuilder())) + // .withAOConfig(AO_CONFIG) .withWallet(JSON.parse(process.env.WALLET_JSON!)) .build(); lastInitTime = currentTime; @@ -239,70 +241,131 @@ export async function cleanupFulfilledEntries( console.log(`${parentLogId} Step 4 completed.`); } +export async function getProviderRequests(PROVIDER_ID: string, parentLogId: string): Promise { + const defaultResponse: GetOpenRandomRequestsResponse = { + providerId: PROVIDER_ID, + activeChallengeRequests: { request_ids: [] }, + activeOutputRequests: { request_ids: [] } + }; + try { + const response = await (await getRandomClient()).getAllProviderActivity(); + const provider = response.find(p => p.provider_id === PROVIDER_ID); + if (!provider) { + console.warn(`${parentLogId} Warning: Provider with ID ${PROVIDER_ID} not found.`); + return defaultResponse; + } + // Attempt to parse fields if they exist, otherwise default to empty arrays + let parsedChallengeRequests: RequestList = { request_ids: [] }; + let parsedOutputRequests: RequestList = { request_ids: [] }; + try { + if (provider.active_challenge_requests) { + //@ts-ignore + parsedChallengeRequests = JSON.parse(provider.active_challenge_requests); + } + } catch (err) { + console.warn(`${parentLogId} Warning: Failed to parse active_challenge_requests:`, err); + } + try { + if (provider.active_output_requests) { + //@ts-ignore + parsedOutputRequests = JSON.parse(provider.active_output_requests); + } + } catch (err) { + console.warn(`${parentLogId} Warning: Failed to parse active_output_requests:`, err); + } + // Only update current_onchain_random if successful + current_onchain_random = provider.random_balance; + const result: GetOpenRandomRequestsResponse = { + providerId: provider.provider_id, + activeChallengeRequests: parsedChallengeRequests, + activeOutputRequests: parsedOutputRequests, + }; + console.log(`${parentLogId} Step 1: Open Requests: ${JSON.stringify(result)}`); + console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${result.activeChallengeRequests.request_ids.length}`); + console.log(`${parentLogId} Step 1: Open Output Requests count: ${result.activeOutputRequests.request_ids.length}`); + return result; + } catch (error) { + console.error(`${parentLogId} Error fetching provider requests: ${error}`); + return defaultResponse; + } +} - - - - - - - -export async function getProviderRequests(PROVIDER_ID: string, parentLogId: string): Promise { - - let openRequests: GetOpenRandomRequestsResponse; - - // Create a function to fetch open requests with a timeout - const fetchOpenRequests = async (): Promise => { - try { - const response = await (await getRandomClient()).getOpenRandomRequests(PROVIDER_ID); - return response; - } catch (error) { - console.error(`${parentLogId} Error fetching requests: ${error}`); - return { /* Return a default or empty response here */ } as GetOpenRandomRequestsResponse; +// Function to check and fetch database entries as needed +export async function checkAndFetchIfNeeded(client: Client) { + try { + // Query current count of usable DB entries + const res = await client.query( + 'SELECT COUNT(*) AS count FROM time_lock_puzzles WHERE request_id IS NULL' + ); + const currentCount = parseInt(res.rows[0].count, 10); + console.log("Total usable DB entries: " + currentCount); + + switch (current_onchain_random) { + case -1: + console.log("Value is -1"); + console.log("Provider has been shut down by USER..."); + console.log("Go to the provider dashboard to turn back on"); + //TODO prepare for shutdown + //TODO the async causes it to ovewrite itself + break; + case -2: + console.log("Value is -2"); + console.log("Provider has been shut down by PROCESS..."); + console.log("This is due to One of the following: "); + console.log("Failing to provide random fast enough (Provider is not responding to random requests and considered unhealthy NO SLASH)"); + console.log("Failing to provide the proof for an outstanding random request within the time. (Provider was likely turned off mid random request SMALL SLASH)" ); + console.log("Failing to provide the correct proof for your original random (Provider was detected as malicious for tampering with the random LARGE SLASH)"); + console.log("Go to the provider dashboard to turn back on"); + break; + case -3: + console.log("Value is -3"); + console.log("Provider has been shut down by PROCESS..."); + console.log("This was likely done as a test or to get the maintainers attention. Contact team if you see this and are not sure why"); + console.log("Go to the provider dashboard to turn back on"); + break; + case -10: + console.log("Value is -10"); + console.log("Provider has been turned on and is starting up OR is not staked yet"); + console.log("Go to the provider dashboard to Stke if you have not yet OR wait fro provider to finish turning on if you have staked already"); + break; + default: + console.log("Value is not -1, -2, or -3"); + console.log("Provider is up and working"); + console.log("Onchain Value is "+ current_onchain_random) + console.log("Local Value is "+ currentCount) + if (Math.abs(current_onchain_random - currentCount) > UNCHAIN_VS_OFFCHAIN_MAX_DIF) { + console.log(`Updating available random values from ${current_onchain_random} to ${currentCount}`); + updateAvailableValuesAsync(currentCount); + } } - }; + if (ongoingRequest) return; // Prevent redundant operations - try { - openRequests = await Promise.race([ - fetchOpenRequests().catch(err => { - throw new Error(`${parentLogId} Fetch Error: ${err}`); - }), - new Promise((_, reject) => - setTimeout(() => reject(new Error('Timeout')), DRYRUNTIMEOUT) - ) - ]); - } catch (error) { - console.log(`${parentLogId} Step 1: ${error}`); - //randclient.setDryRunAsMessage(true); - console.log("Removed this as its spamming") - console.log("Switching dryrun off"); - openRequests = await fetchOpenRequests(); // Retry request - } + // Check if more entries are needed + if (currentCount >= MINIMUM_ENTRIES) return; + getMoreRandom(currentCount) + ongoingRequest = true; - if(openRequests.toString().includes("not found")){ - return false + } catch (error) { + console.error('Error during check and fetch:', error); + } finally { + ongoingRequest = false; // Allow future operations } - console.log(`${parentLogId} Step 1: Open Requests: ${JSON.stringify(openRequests)}`); - console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${openRequests.activeChallengeRequests.request_ids.length}`); - console.log(`${parentLogId} Step 1: Open Challenge Requests count: ${openRequests.activeOutputRequests.request_ids.length}`); - return openRequests; } export function updateAvailableValuesAsync(currentCount: number) { return (async () => { try { - const randomClient = await getRandomClient(); - await randomClient.updateProviderAvailableValues(currentCount); + await (await getRandomClient()).updateProviderAvailableValues(currentCount); console.log(`Updated provider values to ${currentCount}`); } catch (error) { console.error("Failed to update provider values:", error); @@ -310,9 +373,11 @@ export function updateAvailableValuesAsync(currentCount: number) { })(); } export async function getProviderAvailableRandomValues(PROVIDER_ID: string): Promise { - try { - const randomClient = await getRandomClient(); - return await randomClient.getProviderAvailableValues(PROVIDER_ID); + try { //TODO remove and clean this up + return { + providerId: PROVIDER_ID, + availibleRandomValues:current_onchain_random + } } catch (error) { console.error(`Error fetching available random values: ${error}`); return {} as GetProviderAvailableValuesResponse; @@ -393,4 +458,15 @@ const { } catch (error) { console.error(`${parentLogId} Error fulfilling random output for request ID: ${requestId}:`, error); } +} + +export async function shutdown() { + try { + const randomClient = await getRandomClient(); + let message = await randomClient.updateProviderAvailableValues(0); + console.log(message); + console.log(`Updated provider values to 0`); + } catch (error) { + console.error("Failed to update provider values:", error); + } } \ No newline at end of file diff --git a/requester/package.json b/requester/package.json index 4d7aa9a..8b8705b 100644 --- a/requester/package.json +++ b/requester/package.json @@ -7,7 +7,8 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "^5.4.16", + "@permaweb/aoconnect": "^0.0.78", + "ao-process-clients": "^6.0.16", "ao-vrf": "file:", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", diff --git a/requester/src/app.ts b/requester/src/app.ts index 047de7f..fef9a8d 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -1,8 +1,9 @@ import { RandomClient, } from "ao-process-clients"; +import { TransferToProviders } from "./extra"; -const RETRY_DELAY_MS = 5000; // 5 seconds +const RETRY_DELAY_MS = 10000; // 10 seconds const PROVIDER_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes const PROVIDER_REQUEST_TIMEOUT = 60 * 1000; // 1 minute const CHANCE_TO_CALL_RANDOM = 1; @@ -10,20 +11,19 @@ const CHANCE_TO_CALL_RANDOM = 1; let cachedProviders: string[] = []; let lastProviderRefresh = 0; -const AO_CONFIG = { - MU_URL: "https://ur-mu.randao.net", - Cu_URL: "https://ur-cu.randao.net", - GATEWAY_URL: "https://arweave.net", -}; +// const AO_CONFIG = { +// MU_URL: "https://ur-mu.randao.net", +// CU_URL: "https://ur-cu.randao.net", +// GATEWAY_URL: "https://arweave.net", +// }; let randomClientInstance: RandomClient | null = null; async function getRandomClient(): Promise { if (!randomClientInstance) { - randomClientInstance = ((await RandomClient.defaultBuilder()) - .withAOConfig(AO_CONFIG)) - // .withProcessId("2ExUldxQ5NA_hnElSWYq0_lCBgeQQPxPhFbWDFihDEY") + randomClientInstance = ((await RandomClient.defaultBuilder())) + //.withAOConfig(AO_CONFIG) .withWallet(JSON.parse(process.env.REQUEST_WALLET_JSON!)) .build(); } @@ -32,6 +32,7 @@ async function getRandomClient(): Promise { + let totalRandomCalled = 0; let totalTimeToFulfill = 0; let fulfilledRequests = 0; @@ -122,7 +123,8 @@ async function main() { const callbackId = `callback-${Date.now()}`; const { providers, count } = await getRandomProviders(randclient); console.log(`Selected ${count} providers:`, providers); - await randclient.createRequest(providers, count, callbackId); + //await randclient.createRequest(providers, count, callbackId); + await TransferToProviders(providers, callbackId) //await randclient.createRequest(["X1tqliRkKnClhVQ4aIeyuOaPTzr5PfnxqAoSdpTzZy8"], 1, "123"); totalRandomCalled++; console.log("Random request initiated. Awaiting request ID in open requests..."); diff --git a/requester/src/extra.ts b/requester/src/extra.ts new file mode 100644 index 0000000..7cb9233 --- /dev/null +++ b/requester/src/extra.ts @@ -0,0 +1,60 @@ +import { connect, createDataItemSigner } from "@permaweb/aoconnect"; + +const { spawn, message, result } = connect({ + MU_URL: "https://ur-mu.randao.net", + CU_URL: "https://ur-cu.randao.net", + MODE: "legacy" +}); +const TOKEN_PROCESS = "kUnKIPp7XS4pBsy1qy7j3S-JJ0ChZ4utbZ-IPxhw_W0" +const RAND_PROCESS = "UujLOtCfyo3uoKfuozS7cQfTe11BdvWR3Slb65zrR7k" +export async function fetchMessageResult( + messageID: string, + processID: string +): Promise<{ Messages: any[]; Spawns: any[]; Output: any[]; Error: any }> { + try { + const response = await result({ + message: messageID, + process: processID, + }); + + return { + Messages: response.Messages || [], + Spawns: response.Spawns || [], + Output: response.Output || [], + Error: response.Error || null + }; + } catch (error: any) { + if (error instanceof SyntaxError && error.message.includes("Unexpected token '<'")) { + console.error("CU timeout ratelimit error"); + await new Promise(resolve => setTimeout(resolve, 60000)); // Wait 60 seconds + } else { + console.error("Error fetching message result:", error); + } + + return { Messages: [], Spawns: [], Output: [], Error: error }; + } +} +export async function TransferToProviders(providerIds: string[], callbackID:string) { + try { + const sentMessage = await message({ + process: TOKEN_PROCESS, + tags: [ + { name: "Action", value: "Transfer" }, + { name: "library", value: "npm install ao-process-clients" }, + { name: "Quantity", value: "100" }, + { name: "Recipient", value: RAND_PROCESS }, + { name: "X-CallbackId", value: callbackID }, + { name: "X-Providers", value: JSON.stringify({ provider_ids: providerIds }) }, + { name: "X-RequestedInputs", value: JSON.stringify({ requested_inputs: providerIds.length }) }, + ], + signer: createDataItemSigner(JSON.parse(process.env.REQUEST_WALLET_JSON!)), + data: "", + }); + + const result = await fetchMessageResult(sentMessage,TOKEN_PROCESS); + return result; + } catch (error) { + console.error("Transfer message failed:", error); + throw error; + } +} \ No newline at end of file From 9c914c0e76d2ecdf1da7d41295847dd12b6dc1c9 Mon Sep 17 00:00:00 2001 From: Ethan Date: Thu, 17 Apr 2025 11:32:23 -0400 Subject: [PATCH 47/51] added --- docker-compose/docker-compose.yml | 2 +- orchestrator/docs/development.md | 2 +- orchestrator/package.json | 2 +- orchestrator/src/app.ts | 2 +- orchestrator/src/helperFunctions.ts | 4 +++- requester/package.json | 2 +- requester/src/app.ts | 2 ++ requester/src/extra.ts | 14 +++++++++----- 8 files changed, 19 insertions(+), 11 deletions(-) diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index b54467e..8455f29 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.4.60 + image: randao/orchestrator:v0.4.65 depends_on: postgres: condition: service_healthy diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index fcff170..37c7765 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.4.60 # You can change this value to any version you want +export VERSION=v0.4.65 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/package.json b/orchestrator/package.json index 11369e6..fdbc234 100644 --- a/orchestrator/package.json +++ b/orchestrator/package.json @@ -7,7 +7,7 @@ "typescript": "^5.6.3" }, "dependencies": { - "ao-process-clients": "^6.0.16", + "ao-process-clients": "^6.0.18", "ao-vrf": "file:", "arweave": "^1.15.5", "aws-sdk": "^2.1692.0", diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 794fef9..1a54a8f 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -15,7 +15,7 @@ export const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.1'; export const DOCKER_MONITORING_TIME= 30000; -export const POLLING_INTERVAL_MS = 500; //0.5 seconds +export const POLLING_INTERVAL_MS = 5000; //5 seconds export const DATABASE_CHECK_TIME = 60000; //60 seconds export const MINIMUM_ENTRIES = 1000; export const DRYRUNTIMEOUT = 30000; // 30 seconds diff --git a/orchestrator/src/helperFunctions.ts b/orchestrator/src/helperFunctions.ts index 7ff242d..1ff00d1 100644 --- a/orchestrator/src/helperFunctions.ts +++ b/orchestrator/src/helperFunctions.ts @@ -12,6 +12,8 @@ let ongoingRequest = false; // const AO_CONFIG = { // MU_URL: "https://ur-mu.randao.net", // CU_URL: "https://ur-cu.randao.net", +// // MU_URL: "https://mu.ao-testnet.xyz", +// // CU_URL: "https://cu.ao-testnet.xyz", // GATEWAY_URL: "https://arweave.net", // }; // Optional: Auto-reinitialize on a timer @@ -24,7 +26,7 @@ export async function getRandomClient(): Promise { Logger.setLogLevel(LogLevel.DEBUG) if (!randomClientInstance || (currentTime - lastInitTime) > REINIT_INTERVAL) { randomClientInstance = ((await RandomClient.defaultBuilder())) - // .withAOConfig(AO_CONFIG) + //.withAOConfig(AO_CONFIG) .withWallet(JSON.parse(process.env.WALLET_JSON!)) .build(); lastInitTime = currentTime; diff --git a/requester/package.json b/requester/package.json index 8b8705b..1cda5f8 100644 --- a/requester/package.json +++ b/requester/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@permaweb/aoconnect": "^0.0.78", - "ao-process-clients": "^6.0.16", + "ao-process-clients": "^6.0.18", "ao-vrf": "file:", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", diff --git a/requester/src/app.ts b/requester/src/app.ts index fef9a8d..7ffd383 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -14,6 +14,8 @@ let lastProviderRefresh = 0; // const AO_CONFIG = { // MU_URL: "https://ur-mu.randao.net", // CU_URL: "https://ur-cu.randao.net", +// // MU_URL: "https://mu.ao-testnet.xyz", +// // CU_URL: "https://cu.ao-testnet.xyz", // GATEWAY_URL: "https://arweave.net", // }; diff --git a/requester/src/extra.ts b/requester/src/extra.ts index 7cb9233..16547fe 100644 --- a/requester/src/extra.ts +++ b/requester/src/extra.ts @@ -1,12 +1,16 @@ import { connect, createDataItemSigner } from "@permaweb/aoconnect"; const { spawn, message, result } = connect({ - MU_URL: "https://ur-mu.randao.net", - CU_URL: "https://ur-cu.randao.net", - MODE: "legacy" + MU_URL: "https://ur-mu.randao.net", + CU_URL: "https://ur-cu.randao.net", + // MU_URL: "https://mu.ao-testnet.xyz", + // CU_URL: "https://cu.ao-testnet.xyz", + GATEWAY_URL: "https://arweave.net", + MODE: "legacy" + }); -const TOKEN_PROCESS = "kUnKIPp7XS4pBsy1qy7j3S-JJ0ChZ4utbZ-IPxhw_W0" -const RAND_PROCESS = "UujLOtCfyo3uoKfuozS7cQfTe11BdvWR3Slb65zrR7k" +const TOKEN_PROCESS = "rPpsRk9Rm8_SJ1JF8m9_zjTalkv9Soaa_5U0tYUloeY" +const RAND_PROCESS = "ZBSQD_GeGUdQAiixxKy9Ag1rgJvJ_yFUGExwjW6mA7E" export async function fetchMessageResult( messageID: string, processID: string From d15ef70b83192204514a736369425378600dae05 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 18 Apr 2025 10:55:28 -0400 Subject: [PATCH 48/51] added raspberry pi puzzle gen --- docker-compose/docker-compose.yml | 2 +- orchestrator/docs/development.md | 2 +- orchestrator/src/app.ts | 4 ++-- puzzle-generator/docs/developing.md | 23 ++++++++++++++++++----- requester/docker | 0 requester/src/app.ts | 2 +- 6 files changed, 23 insertions(+), 10 deletions(-) delete mode 100644 requester/docker diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 8455f29..7727bcc 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.4.65 + image: randao/orchestrator:v0.4.66 depends_on: postgres: condition: service_healthy diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index 37c7765..f59f8aa 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.4.65 # You can change this value to any version you want +export VERSION=v0.4.66 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 1a54a8f..88b64d5 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -11,11 +11,11 @@ export const docker = new Docker(); export const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); export const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; export const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "backend"; -export const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.1'; +export const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.2'; export const DOCKER_MONITORING_TIME= 30000; -export const POLLING_INTERVAL_MS = 5000; //5 seconds +export const POLLING_INTERVAL_MS = 2500; //2.5 seconds export const DATABASE_CHECK_TIME = 60000; //60 seconds export const MINIMUM_ENTRIES = 1000; export const DRYRUNTIMEOUT = 30000; // 30 seconds diff --git a/puzzle-generator/docs/developing.md b/puzzle-generator/docs/developing.md index d15c15f..6a6291b 100644 --- a/puzzle-generator/docs/developing.md +++ b/puzzle-generator/docs/developing.md @@ -59,12 +59,25 @@ pytest --cov=src -# Build the Docker image with the version tag -docker build -t randao/puzzle-gen:latest -t randao/puzzle-gen:v0.1.1 . +# Set version as an environment variable +export VERSION=v0.1.5 # Change this value as needed -# Log in to Docker +# Initial build and tagging for local testing +docker build -t randao/puzzle-gen:latest -t randao/puzzle-gen:$VERSION . + +# Log in to Docker Hub (optional, remove if already logged in) docker login -# Push the image with the version tag +# Push local builds docker push randao/puzzle-gen:latest -docker push randao/puzzle-gen:v0.1.1 \ No newline at end of file +docker push randao/puzzle-gen:$VERSION + +# Set up and use Docker buildx builder (if not already created) +docker buildx create --name arm-builder --use || docker buildx use arm-builder +docker buildx inspect --bootstrap + +# Multi-platform build for ARM64 and AMD64, and push to Docker Hub +docker buildx build --platform linux/amd64,linux/arm64 \ + -t randao/puzzle-gen:latest \ + -t randao/puzzle-gen:$VERSION \ + --push . diff --git a/requester/docker b/requester/docker deleted file mode 100644 index e69de29..0000000 diff --git a/requester/src/app.ts b/requester/src/app.ts index 7ffd383..bff7611 100644 --- a/requester/src/app.ts +++ b/requester/src/app.ts @@ -3,7 +3,7 @@ import { } from "ao-process-clients"; import { TransferToProviders } from "./extra"; -const RETRY_DELAY_MS = 10000; // 10 seconds +const RETRY_DELAY_MS = 3000; // 3 seconds const PROVIDER_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes const PROVIDER_REQUEST_TIMEOUT = 60 * 1000; // 1 minute const CHANCE_TO_CALL_RANDOM = 1; From 695caf0d01d42364aaa7f66315096518fc7bf6c0 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 18 Apr 2025 11:42:32 -0400 Subject: [PATCH 49/51] added raspberry pi puzzle gen2 --- docker-compose/docker-compose.yml | 2 +- orchestrator/docs/development.md | 2 +- orchestrator/src/app.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 7727bcc..4490714 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.4.66 + image: randao/orchestrator:v0.4.67 depends_on: postgres: condition: service_healthy diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index f59f8aa..11173a5 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.4.66 # You can change this value to any version you want +export VERSION=v0.4.67 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 88b64d5..1c5131d 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -11,7 +11,7 @@ export const docker = new Docker(); export const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); export const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; export const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "backend"; -export const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.2'; +export const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.5'; export const DOCKER_MONITORING_TIME= 30000; From 0e9d11fdd50c55395127680fdf5e33b55dcd69a5 Mon Sep 17 00:00:00 2001 From: Ethan Date: Fri, 18 Apr 2025 12:00:00 -0400 Subject: [PATCH 50/51] added raspberry pi puzzle gen3 --- docker-compose/docker-compose.yml | 3 +- orchestrator/docs/development.md | 2 +- orchestrator/src/app.ts | 1 - orchestrator/src/containerManagment.ts | 82 +++++++++++--------------- 4 files changed, 36 insertions(+), 52 deletions(-) diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 4490714..a76a590 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -18,7 +18,7 @@ services: retries: 5 orchestrator: - image: randao/orchestrator:v0.4.67 + image: randao/orchestrator:v0.4.68 depends_on: postgres: condition: service_healthy @@ -28,7 +28,6 @@ services: DB_USER: ${DB_USER:-myuser} DB_PASSWORD: ${DB_PASSWORD:-mypassword} DB_NAME: ${DB_NAME:-mydatabase} - ENVIRONMENT: local PATH_TO_WALLET: /app/wallet.json # Path inside the container WALLET_JSON: ${WALLET_JSON} DOCKER_NETWORK: backend # Passing the network name diff --git a/orchestrator/docs/development.md b/orchestrator/docs/development.md index 11173a5..d4a3f0a 100644 --- a/orchestrator/docs/development.md +++ b/orchestrator/docs/development.md @@ -16,7 +16,7 @@ npx ts-node src/clear_outputs.ts # Export version as an environment variable -export VERSION=v0.4.67 # You can change this value to any version you want +export VERSION=v0.4.68 # You can change this value to any version you want # Build the Docker image with the version tag docker build -t randao/orchestrator:latest -t randao/orchestrator:$VERSION . diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 1c5131d..5a8806c 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -9,7 +9,6 @@ import {monitorDockerContainers } from './containerManagment.js'; export const docker = new Docker(); export const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); -export const ENVIRONMENT = process.env.ENVIRONMENT || 'local'; export const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "backend"; export const TIME_PUZZLE_JOB_IMAGE = 'randao/puzzle-gen:v0.1.5'; diff --git a/orchestrator/src/containerManagment.ts b/orchestrator/src/containerManagment.ts index 026a035..a9ac742 100644 --- a/orchestrator/src/containerManagment.ts +++ b/orchestrator/src/containerManagment.ts @@ -1,4 +1,4 @@ -import { docker, DOCKER_NETWORK, ecs, ENVIRONMENT, MINIMUM_ENTRIES, TIME_PUZZLE_JOB_IMAGE, UNCHAIN_VS_OFFCHAIN_MAX_DIF } from "./app"; +import { docker, DOCKER_NETWORK, ecs, MINIMUM_ENTRIES, TIME_PUZZLE_JOB_IMAGE, UNCHAIN_VS_OFFCHAIN_MAX_DIF } from "./app"; import AWS from 'aws-sdk'; import { dbConfig } from "./db_tools"; export interface NetworkConfig { @@ -9,6 +9,7 @@ export interface NetworkConfig { let spotInterruptions = 0; // Global variables to track polling status let pulledDockerimage = false; +let pullingImagePromise: Promise | null = null; // Add at the top-level scope (module-global) // Cache for network configuration let cachedNetworkConfig: NetworkConfig | null = null; const ongoingContainers = new Set(); // Track container IDs of running Docker containers @@ -82,58 +83,49 @@ export async function launchVDFTask( export async function triggerTimePuzzleJobPod(randomCount: number): Promise { - if (ENVIRONMENT === 'cloud') { - try { - console.log("Cloud environment detected. Launching ECS task."); - - // Get or refresh network configuration - if (!cachedNetworkConfig) { - console.log("Fetching network configuration..."); - cachedNetworkConfig = await getNetworkConfig(ecs); - console.log("Network config:", cachedNetworkConfig); - } + const containerName = `puzzle-gen_job_${Date.now()}_${Math.floor(Math.random() * 100000)}`; - const taskArn = await launchVDFTask(ecs, cachedNetworkConfig, randomCount); - if (taskArn) { - ongoingContainers.add(taskArn); - console.log(`ECS task started successfully: ${taskArn}`); - return taskArn; - } - return null; - } catch (error: any) { - if (error?.code === 'SpotCapacityNotAvailableException') { - spotInterruptions++; - console.log(`Spot instance reclaimed by AWS. Total interruptions so far: ${spotInterruptions}`); - } else { - console.error("Error launching ECS task:", error); - // Reset network config cache on error to force refresh on next attempt - cachedNetworkConfig = null; - } + if (ongoingContainers.size > 0) { + console.log("A puzzle-gen container is already running. Skipping new container launch."); return null; } - } else { - const containerName = `puzzle-gen_job_${Date.now()}_${Math.floor(Math.random() * 100000)}`; - console.log(`Starting Docker container with name: ${containerName}`); - try { - if (!pulledDockerimage) { - console.log(`Pulling image: ${TIME_PUZZLE_JOB_IMAGE}`); - await new Promise((resolve, reject) => { - docker.pull(TIME_PUZZLE_JOB_IMAGE, (err: any, stream: NodeJS.ReadableStream) => { - if (err) { - return reject(err); + + // Ensure only one pull operation at a time + if (!pulledDockerimage) { + if (!pullingImagePromise) { + pullingImagePromise = new Promise((resolve, reject) => { + console.log(`Pulling image: ${TIME_PUZZLE_JOB_IMAGE}`); + docker.pull(TIME_PUZZLE_JOB_IMAGE, (err: Error | null, stream: NodeJS.ReadableStream | undefined) => { + if (err || !stream) { + pullingImagePromise = null; // reset on error + return reject(err || new Error("Docker stream undefined")); } - docker.modem.followProgress(stream, (doneErr) => { - if (doneErr) reject(doneErr); - else resolve(true); + docker.modem.followProgress(stream, (doneErr: Error | null) => { + if (doneErr) { + pullingImagePromise = null; // reset on error + reject(doneErr); + } else { + pulledDockerimage = true; // Mark as pulled after success + resolve(); + } }); }); }); - pulledDockerimage = true; } + try { + await pullingImagePromise; + } catch (error) { + console.error(`Failed to pull Docker image:`, error); + pullingImagePromise = null; + return null; + } + } + + console.log(`Starting Docker container with name: ${containerName}`); + try { const container = await docker.createContainer({ Image: TIME_PUZZLE_JOB_IMAGE, Cmd: ['sh', '-c', `python3 main.py ${randomCount}`], - Env: [ `DATABASE_TYPE=postgresql`, `DATABASE_HOST=${dbConfig.host}`, @@ -156,7 +148,6 @@ export async function triggerTimePuzzleJobPod(randomCount: number): Promise { // Function to wait for Docker containers to complete and remove them from tracking export async function monitorDockerContainers(): Promise { if (ongoingContainers.size === 0) return; - if (ENVIRONMENT === 'cloud') { - await monitorECSTasks(); - } else { - for (const containerId of ongoingContainers) { try { const container = docker.getContainer(containerId); @@ -248,7 +235,6 @@ export async function monitorDockerContainers(): Promise { ongoingContainers.delete(containerId); // Remove from tracking if there's an error (e.g., container not found) } } - } } // Helper function to type guard Docker errors From 27bd73dc3238cbac5992b1cf94c5a8e2c405358a Mon Sep 17 00:00:00 2001 From: Michael Buhler Date: Wed, 7 May 2025 16:45:10 +0700 Subject: [PATCH 51/51] remove the need for WALLET_JSON env var --- docker-compose/.env.example | 11 ----------- docker-compose/docker-compose.yml | 1 - orchestrator/src/app.ts | 12 +++++++----- orchestrator/src/helperFunctions.ts | 3 ++- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/docker-compose/.env.example b/docker-compose/.env.example index 62a84ab..512557e 100644 --- a/docker-compose/.env.example +++ b/docker-compose/.env.example @@ -2,14 +2,3 @@ DB_USER=myuser DB_PASSWORD=mypassword DB_NAME=mydatabase DOCKER_NETWORK=backend -WALLET_JSON = '{ - "kty": "RSA", - "e": "test", - "n": "test", - "d": "test", - "p": "test", - "q": "test", - "dp": "test", - "dq": "test", - "qi": "test" -}' diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index a76a590..da14667 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -29,7 +29,6 @@ services: DB_PASSWORD: ${DB_PASSWORD:-mypassword} DB_NAME: ${DB_NAME:-mydatabase} PATH_TO_WALLET: /app/wallet.json # Path inside the container - WALLET_JSON: ${WALLET_JSON} DOCKER_NETWORK: backend # Passing the network name networks: - backend diff --git a/orchestrator/src/app.ts b/orchestrator/src/app.ts index 5a8806c..4c5720a 100644 --- a/orchestrator/src/app.ts +++ b/orchestrator/src/app.ts @@ -1,3 +1,4 @@ +import { readFile } from 'node:fs/promises'; import Docker from 'dockerode'; import AWS from 'aws-sdk'; import { connectWithRetry, setupDatabase } from './db_tools.js'; @@ -5,7 +6,10 @@ import Arweave from 'arweave'; import { checkAndFetchIfNeeded, cleanupFulfilledEntries, getProviderRequests, processChallengeRequests, processOutputRequests, shutdown } from './helperFunctions.js'; import {monitorDockerContainers } from './containerManagment.js'; - +if (!process.env.PATH_TO_WALLET) { + console.error("Env var PATH_TO_WALLET is not set!"); + process.exit(1); +} export const docker = new Docker(); export const ecs = new AWS.ECS({ region: process.env.AWS_REGION || 'us-east-1' }); @@ -126,10 +130,8 @@ async function run(): Promise { const client = await connectWithRetry(); await setupDatabase(client); - arweave.wallets.jwkToAddress(JSON.parse(process.env.WALLET_JSON!)).then((address) => { - console.log(address); - PROVIDER_ID = address - }); + const providerAddress = arweave.wallets.jwkToAddress(JSON.parse(await readFile(process.env.PATH_TO_WALLET!, 'utf8'))); + console.log('Provider address:', providerAddress); // setInterval(async () => { // const res = await client.query('SELECT COUNT(*) as count FROM time_lock_puzzles'); diff --git a/orchestrator/src/helperFunctions.ts b/orchestrator/src/helperFunctions.ts index 1ff00d1..21def45 100644 --- a/orchestrator/src/helperFunctions.ts +++ b/orchestrator/src/helperFunctions.ts @@ -1,3 +1,4 @@ +import { readFile } from 'node:fs/promises'; import { GetOpenRandomRequestsResponse, GetProviderAvailableValuesResponse, Logger, LogLevel, RandomClient, RequestList } from "ao-process-clients"; import { Client } from "pg"; import { COMPLETION_RETENTION_PERIOD_MS, MINIMUM_ENTRIES, UNCHAIN_VS_OFFCHAIN_MAX_DIF } from "./app"; @@ -27,7 +28,7 @@ export async function getRandomClient(): Promise { if (!randomClientInstance || (currentTime - lastInitTime) > REINIT_INTERVAL) { randomClientInstance = ((await RandomClient.defaultBuilder())) //.withAOConfig(AO_CONFIG) - .withWallet(JSON.parse(process.env.WALLET_JSON!)) + .withWallet(JSON.parse(await readFile(process.env.PATH_TO_WALLET!, 'utf8'))) .build(); lastInitTime = currentTime; }