From f5f40eb79ce082c1111a34867fe91cac5e0d71c9 Mon Sep 17 00:00:00 2001 From: darwincereska Date: Sat, 20 Dec 2025 03:03:41 -0500 Subject: [PATCH] reset: wiped and rewriting in go --- server/Dockerfile | 67 ---- server/README.md | 0 server/build.gradle.kts | 130 ------- server/docker-compose.yml | 82 ---- server/gradle.properties | 25 -- server/settings.gradle.kts | 0 server/src/main/kotlin/org/ccoin/Server.kt | 258 ------------- .../kotlin/org/ccoin/config/DatabaseConfig.kt | 76 ---- .../kotlin/org/ccoin/config/ServerConfig.kt | 88 ----- .../org/ccoin/database/DatabaseMigrations.kt | 0 .../main/kotlin/org/ccoin/database/Tables.kt | 70 ---- .../org/ccoin/exceptions/CCoinException.kt | 8 - .../exceptions/InsufficientFundsException.kt | 10 - .../exceptions/InvalidTransactionException.kt | 9 - .../exceptions/WalletNotFoundException.kt | 8 - .../kotlin/org/ccoin/models/ApiResponse.kt | 80 ---- .../src/main/kotlin/org/ccoin/models/Block.kt | 61 --- .../kotlin/org/ccoin/models/Transaction.kt | 43 --- .../main/kotlin/org/ccoin/models/Wallet.kt | 30 -- .../main/kotlin/org/ccoin/routes/ApiRoutes.kt | 208 ----------- .../kotlin/org/ccoin/routes/BlockRoutes.kt | 349 ------------------ .../kotlin/org/ccoin/routes/HealthRoutes.kt | 315 ---------------- .../kotlin/org/ccoin/routes/MiningRoutes.kt | 266 ------------- .../org/ccoin/routes/TransactionRoutes.kt | 242 ------------ .../kotlin/org/ccoin/routes/WalletRoutes.kt | 214 ----------- .../kotlin/org/ccoin/services/BlockService.kt | 218 ----------- .../org/ccoin/services/MiningService.kt | 185 ---------- .../org/ccoin/services/TransactionService.kt | 243 ------------ .../org/ccoin/services/ValidationService.kt | 201 ---------- .../org/ccoin/services/WalletService.kt | 150 -------- .../kotlin/org/ccoin/utils/CryptoUtils.kt | 129 ------- .../main/kotlin/org/ccoin/utils/Extensions.kt | 0 .../main/kotlin/org/ccoin/utils/HashUtils.kt | 80 ---- server/src/main/resources/application.conf | 9 - .../db/migration/V1__Create_wallets_table.sql | 0 .../V2__Create_transactions_table.sql | 0 .../db/migration/V3__Create_blocks_table.sql | 0 .../db/migration/V4__Add_indexes.sql | 0 server/src/main/resources/logback.xml | 15 - .../src/test/kotlin/org/ccoin/ServerTest.kt | 0 .../org/ccoin/services/MiningServiceTest.kt | 0 .../ccoin/services/TransactionServiceTest.kt | 0 .../org/ccoin/services/WalletServiceTest.kt | 0 .../kotlin/org/ccoin/utils/CryptoUtilsTest.kt | 0 44 files changed, 3869 deletions(-) delete mode 100644 server/Dockerfile delete mode 100644 server/README.md delete mode 100644 server/build.gradle.kts delete mode 100644 server/docker-compose.yml delete mode 100644 server/gradle.properties delete mode 100644 server/settings.gradle.kts delete mode 100644 server/src/main/kotlin/org/ccoin/Server.kt delete mode 100644 server/src/main/kotlin/org/ccoin/config/DatabaseConfig.kt delete mode 100644 server/src/main/kotlin/org/ccoin/config/ServerConfig.kt delete mode 100644 server/src/main/kotlin/org/ccoin/database/DatabaseMigrations.kt delete mode 100644 server/src/main/kotlin/org/ccoin/database/Tables.kt delete mode 100644 server/src/main/kotlin/org/ccoin/exceptions/CCoinException.kt delete mode 100644 server/src/main/kotlin/org/ccoin/exceptions/InsufficientFundsException.kt delete mode 100644 server/src/main/kotlin/org/ccoin/exceptions/InvalidTransactionException.kt delete mode 100644 server/src/main/kotlin/org/ccoin/exceptions/WalletNotFoundException.kt delete mode 100644 server/src/main/kotlin/org/ccoin/models/ApiResponse.kt delete mode 100644 server/src/main/kotlin/org/ccoin/models/Block.kt delete mode 100644 server/src/main/kotlin/org/ccoin/models/Transaction.kt delete mode 100644 server/src/main/kotlin/org/ccoin/models/Wallet.kt delete mode 100644 server/src/main/kotlin/org/ccoin/routes/ApiRoutes.kt delete mode 100644 server/src/main/kotlin/org/ccoin/routes/BlockRoutes.kt delete mode 100644 server/src/main/kotlin/org/ccoin/routes/HealthRoutes.kt delete mode 100644 server/src/main/kotlin/org/ccoin/routes/MiningRoutes.kt delete mode 100644 server/src/main/kotlin/org/ccoin/routes/TransactionRoutes.kt delete mode 100644 server/src/main/kotlin/org/ccoin/routes/WalletRoutes.kt delete mode 100644 server/src/main/kotlin/org/ccoin/services/BlockService.kt delete mode 100644 server/src/main/kotlin/org/ccoin/services/MiningService.kt delete mode 100644 server/src/main/kotlin/org/ccoin/services/TransactionService.kt delete mode 100644 server/src/main/kotlin/org/ccoin/services/ValidationService.kt delete mode 100644 server/src/main/kotlin/org/ccoin/services/WalletService.kt delete mode 100644 server/src/main/kotlin/org/ccoin/utils/CryptoUtils.kt delete mode 100644 server/src/main/kotlin/org/ccoin/utils/Extensions.kt delete mode 100644 server/src/main/kotlin/org/ccoin/utils/HashUtils.kt delete mode 100644 server/src/main/resources/application.conf delete mode 100644 server/src/main/resources/db/migration/V1__Create_wallets_table.sql delete mode 100644 server/src/main/resources/db/migration/V2__Create_transactions_table.sql delete mode 100644 server/src/main/resources/db/migration/V3__Create_blocks_table.sql delete mode 100644 server/src/main/resources/db/migration/V4__Add_indexes.sql delete mode 100644 server/src/main/resources/logback.xml delete mode 100644 server/src/test/kotlin/org/ccoin/ServerTest.kt delete mode 100644 server/src/test/kotlin/org/ccoin/services/MiningServiceTest.kt delete mode 100644 server/src/test/kotlin/org/ccoin/services/TransactionServiceTest.kt delete mode 100644 server/src/test/kotlin/org/ccoin/services/WalletServiceTest.kt delete mode 100644 server/src/test/kotlin/org/ccoin/utils/CryptoUtilsTest.kt diff --git a/server/Dockerfile b/server/Dockerfile deleted file mode 100644 index 5cabd14..0000000 --- a/server/Dockerfile +++ /dev/null @@ -1,67 +0,0 @@ -# Multi-stage build for Java 24 -FROM openjdk:24-jdk-slim AS builder - -# Install required packages -RUN apt-get update && apt-get install -y \ - curl \ - unzip \ - && rm -rf /var/lib/apt/lists/* - -# Set working directory -WORKDIR /app - -# Copy gradle wrapper and build files -COPY gradle/ gradle/ -COPY gradlew . -COPY gradle.properties . -COPY settings.gradle.kts . -COPY build.gradle.kts . - -# Make gradlew executable -RUN chmod +x gradlew - -# Download dependencies (for better caching) -RUN ./gradlew dependencies --no-daemon - -# Copy source code -COPY src/ src/ - -# Build the application -RUN ./gradlew shadowJar --no-daemon - -# Runtime stage -FROM openjdk:24-jdk-slim - -# Install required runtime packages -RUN apt-get update && apt-get install -y \ - curl \ - && rm -rf /var/lib/apt/lists/* - -# Create app user -RUN groupadd -r ccoin && useradd -r -g ccoin ccoin - -# Set working directory -WORKDIR /app - -# Copy the built JAR from builder stage -COPY --from=builder /app/build/libs/*.jar app.jar - -# Change ownership to app user -RUN chown -R ccoin:ccoin /app - -# Switch to app user -USER ccoin - -# Expose port -EXPOSE 8080 - -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:8080/health || exit 1 - -# JVM options for production -ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:+UseStringDeduplication" - -# Run the application -CMD ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] - diff --git a/server/README.md b/server/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/server/build.gradle.kts b/server/build.gradle.kts deleted file mode 100644 index 601725c..0000000 --- a/server/build.gradle.kts +++ /dev/null @@ -1,130 +0,0 @@ -import java.time.Instant -val ktor_version: String by project -val kotlin_version: String by project -val logback_version: String by project -val exposed_version: String by project -val postgresql_version: String by project -val hikari_version: String by project -val flyway_version: String by project -val bouncycastle_version: String by project - -plugins { - kotlin("jvm") version "2.2.21" - kotlin("plugin.serialization") version "2.2.21" - id("io.ktor.plugin") version "3.3.3" - id("com.gradleup.shadow") version "9.3.0" - id("org.flywaydb.flyway") version "11.19.0" - id("com.github.gmazzo.buildconfig") version "6.0.6" - application -} - -group = "org.ccoin" -version = "1.0.0" - -buildConfig { - buildConfigField("String", "VERSION", "\"${project.version}\"") - buildConfigField("BASE_URL", "https://ccoin.darwincereska.dev") - buildConfigField("String", "BUILD_TIME", "\"${Instant.now()}\"") - packageName("org.ccoin") -} - -application { - mainClass.set("org.ccoin.ServerKt") - - val isDevelopment: Boolean = project.ext.has("development") - applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") -} - -repositories { - mavenCentral() -} - -dependencies { - // Ktor server - implementation("io.ktor:ktor-server-core-jvm:$ktor_version") - implementation("io.ktor:ktor-server-netty-jvm:$ktor_version") - implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version") - implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version") - implementation("io.ktor:ktor-server-cors-jvm:$ktor_version") - implementation("io.ktor:ktor-server-call-logging-jvm:$ktor_version") - implementation("io.ktor:ktor-server-status-pages-jvm:$ktor_version") - implementation("io.ktor:ktor-server-compression-jvm:$ktor_version") - implementation("io.ktor:ktor-server-default-headers-jvm:$ktor_version") - implementation("io.ktor:ktor-server-host-common-jvm:$ktor_version") - implementation("io.ktor:ktor-server-config-yaml:$ktor_version") - - // Database - implementation("org.jetbrains.exposed:exposed-core:$exposed_version") - implementation("org.jetbrains.exposed:exposed-dao:$exposed_version") - implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version") - implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version") - implementation("org.postgresql:postgresql:$postgresql_version") - implementation("com.zaxxer:HikariCP:$hikari_version") - implementation("org.flywaydb:flyway-core:$flyway_version") - implementation("org.flywaydb:flyway-database-postgresql:$flyway_version") - - // Logging - implementation("ch.qos.logback:logback-classic:$logback_version") - implementation("io.github.oshai:kotlin-logging-jvm:7.0.0") - - // Crypto utilities - implementation("org.bouncycastle:bcprov-jdk18on:$bouncycastle_version") - implementation("org.bouncycastle:bcpkix-jdk18on:$bouncycastle_version") - - // JSON - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") - implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1") - - // Coroutines - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") - - // Testing - testImplementation("io.ktor:ktor-server-test-host-jvm:$ktor_version") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") - testImplementation("io.mockk:mockk:1.13.13") - testImplementation("org.junit.jupiter:junit-jupiter:5.11.3") - testImplementation("org.testcontainers:testcontainers:1.20.3") - testImplementation("org.testcontainers:postgresql:1.20.3") - testImplementation("org.testcontainers:junit-jupiter:1.20.3") -} - -tasks.withType { - useJUnitPlatform() -} - -// Fat JAR configuration for Docker -tasks.jar { - enabled = false -} - -tasks.shadowJar { - archiveClassifier.set("") - manifest { - attributes["Main-Class"] = "org.ccoin.ServerKt" - } - mergeServiceFiles() -} - -// Flyway configuration -flyway { - url = "jdbc:postgresql://localhost:5432/ccoin" - user = "ccoin" - password = "ccoin" - locations = arrayOf("classpath:db/migration") - baselineOnMigrate = true - validateOnMigrate = true -} - -// Docker build task -tasks.register("buildDocker") { - dependsOn("shadowJar") - commandLine("docker", "build", "-t", "ccoin-server:latest", ".") -} - -// Development task -tasks.register("dev") { - dependsOn("run") - doFirst { - project.ext.set("development", true) - } -} diff --git a/server/docker-compose.yml b/server/docker-compose.yml deleted file mode 100644 index f422da3..0000000 --- a/server/docker-compose.yml +++ /dev/null @@ -1,82 +0,0 @@ -services: - # Postgres Database - postgres: - image: postgres:17-alpine - container_name: ccoin-postgres - restart: unless-stopped - environment: - POSTGRES_DB: ccoin - POSTGRES_USER: ccoin - POSTGRES_PASSWORD: ccoin - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - - ./docker/postgres/init:/docker-entrypoint-initdb.d - networks: - - ccoin-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ccoin -d ccoin"] - interval: 10s - timeout: 5s - retries: 5 - start_period: 30s - - # CCoin server - ccoin-server: - build: - context: . - dockerfile: Dockerfile - container_name: ccoin-server - restart: unless-stopped - ports: - - "8080:8080" - depends_on: - postgres: - condition: service_healthy - environment: - # Database configuration - DATABASE_URL: jdbc:postgresql://postgres:5432/ccoin - DATABASE_USER: ccoin - DATABASE_PASSWORD: ccoin - DATABASE_POOL_SIZE: 20 - - # Server configuration - SERVER_HOST: 0.0.0.0 - SERVER_PORT: 8080 - - # Mining configuration - MINING_DIFFICULTY: 4 - MINING_REWARD: 50.0 - BLOCK_TIME_TARGET: 600000 - - # Security - JWT_SECRET: your-super-secret-jwt-key-change-this-in-production - - # Logging - LOG_LEVEL: INFO - - # Development - DEVELOPMENT_MODE: false - volumes: - - ./logs:/app/logs - networks: - - ccoin-network - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8080/health"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 60s - -volumes: - postgres_data: - driver: local - -networks: - ccoin-network: - driver: bridge - ipam: - config: - - subnet: 172.20.0.0/16 diff --git a/server/gradle.properties b/server/gradle.properties deleted file mode 100644 index 278a6f6..0000000 --- a/server/gradle.properties +++ /dev/null @@ -1,25 +0,0 @@ -# Ktor -ktor_version=3.3.3 -kotlin_version=2.2.21 - -# Database -exposed_version=0.56.0 -postgresql_version=42.7.4 -hikari_version=6.0.0 -flyway_version=11.19.0 - -# Crypto -bouncycastle_version=1.78.1 - -# Logging -logback_version=1.5.12 - -# Gradle -kotlin.code.style=official -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -org.gradle.caching=true -org.gradle.parallel=true -org.gradle.configuration-cache=false - -# Application -ccoin.version=1.0.0 diff --git a/server/settings.gradle.kts b/server/settings.gradle.kts deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/main/kotlin/org/ccoin/Server.kt b/server/src/main/kotlin/org/ccoin/Server.kt deleted file mode 100644 index b1d725f..0000000 --- a/server/src/main/kotlin/org/ccoin/Server.kt +++ /dev/null @@ -1,258 +0,0 @@ -package org.ccoin - -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import io.ktor.server.application.* -import io.ktor.server.engine.* -import io.ktor.server.netty.* -import io.ktor.server.plugins.calllogging.* -import io.ktor.server.plugins.compression.* -import io.ktor.server.plugins.contentnegotiation.* -import io.ktor.server.plugins.cors.routing.* -import io.ktor.server.plugins.defaultheaders.* -import io.ktor.server.plugins.statuspages.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import kotlinx.serialization.json.Json -import org.ccoin.config.DatabaseConfig -import org.ccoin.config.ServerConfig -import org.ccoin.exceptions.CCoinException -import org.ccoin.exceptions.InsufficientFundsException -import org.ccoin.exceptions.InvalidTransactionException -import org.ccoin.exceptions.WalletNotFoundException -import org.ccoin.routes.* -import org.slf4j.LoggerFactory -import org.slf4j.event.Level -import kotlinx.serialization.Serializable - -fun main() { - val logger = LoggerFactory.getLogger("CCoinServer") - - try { - logger.info("Starting CCoin Server...") - - // Validate configuration - ServerConfig.validateConfig() - - // Initialize database - DatabaseConfig.init() - - // Start server - embeddedServer( - Netty, - port = ServerConfig.port, - host = ServerConfig.host, - module = Application::module - ).start(wait = true) - - } catch (e: Exception) { - logger.error("Failed to start CCoin Server", e) - throw e - } -} - -fun Application.module() { - val logger = LoggerFactory.getLogger("CCoinServer") - - logger.info("Configuring CCoin Server modules...") - - configureSerialization() - configureHTTP() - configureStatusPages() - configureRouting() - - logger.info("CCoin Server started successfully on ${ServerConfig.host}:${ServerConfig.port}") -} - -fun Application.configureSerialization() { - install(ContentNegotiation) { - json(Json { - prettyPrint = true - isLenient = true - ignoreUnknownKeys = true - encodeDefaults = false - }) - } -} - -fun Application.configureHTTP() { - install(CORS) { - allowMethod(HttpMethod.Options) - allowMethod(HttpMethod.Put) - allowMethod(HttpMethod.Delete) - allowMethod(HttpMethod.Patch) - allowHeader(HttpHeaders.Authorization) - allowHeader(HttpHeaders.ContentType) - allowHeader(HttpHeaders.AccessControlAllowOrigin) - allowCredentials = true - anyHost() // For development - restrict in production - } - - install(CallLogging) { - level = Level.INFO - filter { call -> call.request.uri.startsWith("/") } - format { call -> - val status = call.response.status() - val httpMethod = call.request.httpMethod.value - val userAgent = call.request.headers["User-Agent"] - val uri = call.request.uri - "$status: $httpMethod $uri - $userAgent" - } - } - - install(Compression) { - gzip { - priority = 1.0 - } - deflate { - priority = 10.0 - minimumSize(1024) - } - } - - install(DefaultHeaders) { - header("X-Engine", "Ktor") - header("X-Service", "CCoin") - header("X-Version", ServerConfig.version) - } -} - -fun Application.configureStatusPages() { - install(StatusPages) { - exception { call, cause -> - call.respond( - HttpStatusCode.NotFound, - mapOf( - "error" to cause.message, - "code" to "WALLET_NOT_FOUND", - "address" to cause.address - ) - ) - } - - exception { call, cause -> - call.respond( - HttpStatusCode.BadRequest, - mapOf( - "error" to cause.message, - "code" to "INSUFFICIENT_FUNDS", - "address" to cause.address, - "requested" to cause.requestedAmount, - "available" to cause.availableBalance - ) - ) - } - - exception { call, cause -> - call.respond( - HttpStatusCode.BadRequest, - mapOf( - "error" to cause.message, - "code" to "INVALID_TRANSACTION", - "transactionHash" to cause.transactionHash - ) - ) - } - - exception { call, cause -> - call.respond( - HttpStatusCode.BadRequest, - mapOf( - "error" to cause.message, - "code" to cause.errorCode - ) - ) - } - - exception { call, cause -> - call.respond( - HttpStatusCode.BadRequest, - mapOf( - "error" to (cause.message ?: "Invalid argument"), - "code" to "INVALID_ARGUMENT" - ) - ) - } - - exception { call, cause -> - val logger = LoggerFactory.getLogger("CCoinServer") - logger.error("Unhandled exception", cause) - - call.respond( - HttpStatusCode.InternalServerError, - mapOf( - "error" to if (ServerConfig.developmentMode) { - cause.message ?: "Internal server error" - } else { - "Internal server error" - }, - "code" to "INTERNAL_ERROR" - ) - ) - } - } -} - -fun Application.configureRouting() { - routing { - // Root endpoint - get("/") { - call.respond( - DefaultResponse( - service = "CCoin Server", - version = ServerConfig.version, - status = "running", - timestamp = System.currentTimeMillis(), - endpoints = listOf( - Endpoint("health", "/health"), - Endpoint("api", "/api/routes"), - Endpoint("wallet", "/wallet"), - Endpoint("transaction", "/transaction"), - Endpoint("mining", "/mining"), - Endpoint("block", "/block") - ) - ) - ) - } - - // API routes - walletRoutes() - transactionRoutes() - miningRoutes() - blockRoutes() - healthRoutes() - apiRoutes() - - // Catch-all for undefined routes - route("{...}") { - handle { - call.respond( - HttpStatusCode.NotFound, - mapOf( - "error" to "Endpoint not found", - "code" to "NOT_FOUND", - "path" to call.request.uri, - "method" to call.request.httpMethod.value, - "availableEndpoints" to "/api/routes" - ) - ) - } - } - } -} - -@Serializable -data class DefaultResponse( - val service: String, - val version: String, - val status: String, - val timestamp: Long, - val endpoints: List -) - -@Serializable -data class Endpoint( - val name: String, - val route: String -) diff --git a/server/src/main/kotlin/org/ccoin/config/DatabaseConfig.kt b/server/src/main/kotlin/org/ccoin/config/DatabaseConfig.kt deleted file mode 100644 index 992a365..0000000 --- a/server/src/main/kotlin/org/ccoin/config/DatabaseConfig.kt +++ /dev/null @@ -1,76 +0,0 @@ -package org.ccoin.config - -import com.zaxxer.hikari.HikariConfig -import com.zaxxer.hikari.HikariDataSource -import org.ccoin.database.Tables -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.transactions.transaction -import org.slf4j.LoggerFactory - -object DatabaseConfig { - private val logger = LoggerFactory.getLogger(DatabaseConfig::class.java) - - fun init() { - logger.info("Initializing database connection...") - - val config = HikariConfig().apply { - driverClassName = "org.postgresql.Driver" - jdbcUrl = System.getenv("DATABASE_URL") ?: "jdbc:postgresql://localhost:5432/ccoin" - username = System.getenv("DATABASE_USER") ?: "ccoin" - password = System.getenv("DATABASE_PASSWORD") ?: "ccoin" - - // Connection pool settings - maximumPoolSize = (System.getenv("DATABASE_POOL_SIZE")?.toIntOrNull() ?: 20) - minimumIdle = 5 - connectionTimeout = 30000 - idleTimeout = 600000 - maxLifetime = 1800000 - - // Performance settings - isAutoCommit = true - - // Connection validation - connectionTestQuery = "SELECT 1" - validationTimeout = 5000 - - // Pool name for monitoring - poolName = "CCoinPool" - } - - try { - val dataSource = HikariDataSource(config) - Database.connect(dataSource) - - logger.info("Database connection established successfully") - - // Create tables if they don't exist - createTables() - } catch(e: Exception) { - logger.error("Failed to initialize database connection", e) - throw e - } - } - - private fun createTables() { - logger.info("Creating database tables if they don't exist...") - - transaction { - SchemaUtils.create( - Tables.Wallets, - Tables.Transactions, - Tables.Blocks - ) - } - - logger.info("Database tables created/verified successfully") - } - - fun getConnectionInfo(): Map { - return mapOf( - "url" to (System.getenv("DATABASE_URL") ?: "jdbc:postgresql://localhost:5432/ccoin"), - "user" to (System.getenv("DATABASE_USER") ?: "ccoin_user"), - "poolSize" to (System.getenv("DATABASE_POOL_SIZE")?.toIntOrNull() ?: 20) - ) - } -} diff --git a/server/src/main/kotlin/org/ccoin/config/ServerConfig.kt b/server/src/main/kotlin/org/ccoin/config/ServerConfig.kt deleted file mode 100644 index 90d279a..0000000 --- a/server/src/main/kotlin/org/ccoin/config/ServerConfig.kt +++ /dev/null @@ -1,88 +0,0 @@ -package org.ccoin.config - -import org.slf4j.LoggerFactory -import org.ccoin.BuildConfig - -object ServerConfig { - private val logger = LoggerFactory.getLogger(ServerConfig::class.java) - - // App Details - val version: String = BuildConfig.VERSION - val buildTime: String = BuildConfig.BUILD_TIME - val baseUrl: String = BuildConfig.BASE_URL - - // Server settings - val host: String = System.getenv("SERVER_HOST") ?: "0.0.0.0" - val port: Int = System.getenv("SERVER_PORT")?.toIntOrNull() ?: 8080 - val developmentMode: Boolean = System.getenv("DEVELOPMENT_MODE")?.toBoolean() ?: true - - // Mining settings - val miningDifficulty: Int = System.getenv("MINING_DIFFICULTY")?.toIntOrNull() ?: 4 - val miningReward: Double = System.getenv("MINING_REWARD")?.toDoubleOrNull() ?: 50.0 - val blockTimeTarget: Long = System.getenv("BLOCK_TIME_TARGET")?.toLongOrNull() ?: 600000L // 10 minutes - - // Transaction settings - val defaultTransactionFee: Double = System.getenv("DEFAULT_TRANSACTION_FEE")?.toDoubleOrNull() ?: 0.01 - val maxTransactionSize: Int = System.getenv("MAX_TRANSACTION_SIZE")?.toIntOrNull() ?: (1024 * 1024) // 1MB - val maxMemoLength: Int = System.getenv("MAX_MEMO_LENGTH")?.toIntOrNull() ?: 256 - - // Security settings - val jwtSecret: String = System.getenv("JWT_SECRET") ?: "change-this-in-production" - val rateLimitRequests: Int = System.getenv("RATE_LIMIT_REQUESTS")?.toIntOrNull() ?: 100 - val rateLimitWindow: Long = System.getenv("RATE_LIMIT_WINDOW")?.toLongOrNull() ?: 60000L // 1 minute - - // Blockchain settings - val maxBlockSize: Int = System.getenv("MAX_BLOCK_SIZE")?.toIntOrNull() ?: 1024 * 1024 // 1MB - val maxTransactionsPerBlock: Int = System.getenv("MAX_TRANSACTIONS_PER_BLOCK")?.toIntOrNull() ?: 1000 - val confirmationsRequired: Int = System.getenv("CONFIRMATIONS_REQUIRED")?.toIntOrNull() ?: 6 - - // API Settings - val maxPageSize: Int = System.getenv("MAX_PAGE_SIZE")?.toIntOrNull() ?: 100 - val defaultPageSize: Int = System.getenv("DEFAULT_PAGE_SIZE")?.toIntOrNull() ?: 50 - val apiTimeout: Long = System.getenv("API_TIMEOUT")?.toLongOrNull() ?: 30000L // 30 seconds - - // Logging settings - val logLevel: String = System.getenv("LOG_LEVEL") ?: "INFO" - - init { - logger.info("Server configuration loaded:") - logger.info("Host: $host") - logger.info("Port: $port") - logger.info("Development mode: $developmentMode") - logger.info("Mining difficulty: $miningDifficulty") - logger.info("Mining reward: $miningReward") - logger.info("Block time target: ${blockTimeTarget}ms") - logger.info("Default transaction fee: $defaultTransactionFee") - logger.info("Confirmations required: $confirmationsRequired") - - if (jwtSecret == "change-this-in-production" && !developmentMode) { - logger.warn("WARNING: Using default JWT secret in production mode!") - } - } - - fun getServerInfo(): Map { - return mapOf( - "host" to host, - "port" to port, - "developmentMode" to developmentMode, - "version" to version, - "miningDifficulty" to miningDifficulty, - "miningReward" to miningReward, - "blockTimeTarget" to blockTimeTarget, - "confirmationsRequired" to confirmationsRequired - ) - } - - fun validateConfig() { - require(port in 1..65535) { "Port must be between 1 and 65535" } - require(miningDifficulty in 1..32) { "Mining difficulty must be between 1 and 32" } - require(miningReward > 0) { "Mining reward must be positive" } - require(blockTimeTarget > 0) { "Block time target must be positive" } - require(defaultTransactionFee >= 0) { "Default transaction fee cannot be negative" } - require(confirmationsRequired > 0) { "Confirmations required must be positive" } - require(maxPageSize > 0) { "Max page size must be positive" } - require(defaultPageSize > 0) { "Default page size must be positive" } - - logger.info("Server configuration validation passed") - } -} diff --git a/server/src/main/kotlin/org/ccoin/database/DatabaseMigrations.kt b/server/src/main/kotlin/org/ccoin/database/DatabaseMigrations.kt deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/main/kotlin/org/ccoin/database/Tables.kt b/server/src/main/kotlin/org/ccoin/database/Tables.kt deleted file mode 100644 index a632fea..0000000 --- a/server/src/main/kotlin/org/ccoin/database/Tables.kt +++ /dev/null @@ -1,70 +0,0 @@ -package org.ccoin.database - -import org.jetbrains.exposed.sql.Table -import org.jetbrains.exposed.sql.javatime.timestamp -import java.math.BigDecimal - -object Tables { - object Wallets : Table("wallets") { - val address = varchar("address", 64) // Format random_word:random_6_digits - val balance = decimal("balance", 20, 8).default(BigDecimal.ZERO) - val label = varchar("label", 255).nullable() - val passwordHash = varchar("password_hash", 64).nullable() - val createdAt = long("created_at") - val lastActivity = long("last_activity").nullable() - - override val primaryKey = PrimaryKey(address) - - init { - index(false, createdAt) - index(false, lastActivity) - } - } - - object Transactions : Table("transactions") { - val hash = varchar("hash", 64) - val fromAddress = varchar("from_address", 64).nullable() - val toAddress = varchar("to_address", 64) - val amount = decimal("amount", 20, 8) - val fee = decimal("fee", 20, 8).default(BigDecimal.ZERO) - val memo = text("memo").nullable() - val blockHash = varchar("block_hash", 64).nullable() - val timestamp = long("timestamp") - val status = varchar("status", 20).default("pending") - val confirmations = integer("confirmations").default(0) - - override val primaryKey = PrimaryKey(hash) - - init { - index(false, fromAddress) - index(false, toAddress) - index(false, blockHash) - index(false, timestamp) - index(false, status) - } - } - - object Blocks : Table("blocks") { - val hash = varchar("hash", 64) - val previousHash = varchar("previous_hash", 64).nullable() - val merkleRoot = varchar("merkle_root", 64) - val timestamp = long("timestamp") - val difficulty = integer("difficulty") - val nonce = long("nonce") - val minerAddress = varchar("miner_address", 64) - val reward = decimal("reward", 20, 8) - val height = integer("height").autoIncrement() - val transactionCount = integer("transaction_count").default(0) - val confirmations = integer("confirmations").default(0) - - override val primaryKey = PrimaryKey(hash) - - init { - index(false, height) - index(false, minerAddress) - index(false, timestamp) - index(false, previousHash) - } - } -} - diff --git a/server/src/main/kotlin/org/ccoin/exceptions/CCoinException.kt b/server/src/main/kotlin/org/ccoin/exceptions/CCoinException.kt deleted file mode 100644 index 561dae3..0000000 --- a/server/src/main/kotlin/org/ccoin/exceptions/CCoinException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.ccoin.exceptions - -/** Base exception class for all CCoin-related exceptions */ -open class CCoinException( - message: String, - cause: Throwable? = null, - val errorCode: String? = null -) : Exception(message, cause) diff --git a/server/src/main/kotlin/org/ccoin/exceptions/InsufficientFundsException.kt b/server/src/main/kotlin/org/ccoin/exceptions/InsufficientFundsException.kt deleted file mode 100644 index 9ea0273..0000000 --- a/server/src/main/kotlin/org/ccoin/exceptions/InsufficientFundsException.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.ccoin.exceptions - -class InsufficientFundsException( - val address: String, - val requestedAmount: Double, - val availableBalance: Double -) : CCoinException( - message = "Insufficient funds in wallet $address. Requested: $requestedAmount, Available: $availableBalance", - errorCode = "INSUFFICIENT_FUNDS" -) diff --git a/server/src/main/kotlin/org/ccoin/exceptions/InvalidTransactionException.kt b/server/src/main/kotlin/org/ccoin/exceptions/InvalidTransactionException.kt deleted file mode 100644 index fa62638..0000000 --- a/server/src/main/kotlin/org/ccoin/exceptions/InvalidTransactionException.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.ccoin.exceptions - -class InvalidTransactionException( - message: String, - val transactionHash: String? = null -) : CCoinException( - message = message, - errorCode = "INVALID_TRANSACTION" -) diff --git a/server/src/main/kotlin/org/ccoin/exceptions/WalletNotFoundException.kt b/server/src/main/kotlin/org/ccoin/exceptions/WalletNotFoundException.kt deleted file mode 100644 index b0993f6..0000000 --- a/server/src/main/kotlin/org/ccoin/exceptions/WalletNotFoundException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.ccoin.exceptions - -class WalletNotFoundException( - val address: String -) : CCoinException( - message = "Wallet with address '$address' not found", - errorCode = "WALLET_NOT_FOUND" -) diff --git a/server/src/main/kotlin/org/ccoin/models/ApiResponse.kt b/server/src/main/kotlin/org/ccoin/models/ApiResponse.kt deleted file mode 100644 index b154d61..0000000 --- a/server/src/main/kotlin/org/ccoin/models/ApiResponse.kt +++ /dev/null @@ -1,80 +0,0 @@ -package org.ccoin.models - -import kotlinx.serialization.Serializable - -@Serializable -data class ApiResponse( - val success: Boolean, - val data: T? = null, - val error: String? = null, - val timestamp: Long = System.currentTimeMillis() -) - -@Serializable -data class ErrorResponse( - val error: String, - val code: String? = null, - val details: Map? = null, - val timestamp: Long = System.currentTimeMillis() -) - -@Serializable -data class SuccessResponse( - val message: String, - val timestamp: Long = System.currentTimeMillis() -) - -@Serializable -data class HealthResponse( - val status: String, - val version: String, - val uptime: Long, - val database: DatabaseHealth, - val blockchain: BlockchainHealth -) - -@Serializable -data class DatabaseHealth( - val connected: Boolean, - val responseTime: Long, - val activeConnections: Int, - val maxConnections: Int -) - -@Serializable -data class BlockchainHealth( - val latestBlock: Int, - val pendingTransactions: Int, - val networkHashRate: Double, - val averageBlockTime: Long -) - -@Serializable -data class PaginationRequest( - val page: Int = 1, - val pageSize: Int = 50, - val sortBy: String? = null, - val sortOrder: SortOrder = SortOrder.DESC -) - -@Serializable -data class PaginatedResponse( - val data: List, - val pagination: PaginationInfo -) - -@Serializable -data class PaginationInfo( - val currentPage: Int, - val pageSize: Int, - val totalItems: Int, - val totalPages: Int, - val hasNext: Boolean, - val hasPrevious: Boolean -) - -@Serializable -enum class SortOrder { - ASC, - DESC -} diff --git a/server/src/main/kotlin/org/ccoin/models/Block.kt b/server/src/main/kotlin/org/ccoin/models/Block.kt deleted file mode 100644 index 9c2e53f..0000000 --- a/server/src/main/kotlin/org/ccoin/models/Block.kt +++ /dev/null @@ -1,61 +0,0 @@ -package org.ccoin.models - -import kotlinx.serialization.Serializable - -@Serializable -data class StartMiningRequest( - val minerAddress: String, // Format: random_word:random_6_digits - val difficulty: Int? = null -) - -@Serializable -data class SubmitMiningRequest( - val minerAddress: String, - val nonce: Long, - val hash: String, - val previousHash: String, - val timestamp: Long -) - -@Serializable -data class BlockResponse( - val hash: String, - val previousHash: String?, - val merkleRoot: String, - val timestamp: Long, - val difficulty: Int, - val nonce: Long, - val minerAddress: String, - val reward: Double, - val height: Int, - val transactionCount: Int, - val confirmations: Int = 0 -) - -@Serializable -data class MiningJobResponse( - val jobId: String, - val target: String, - val difficulty: Int, - val previousHash: String, - val height: Int, - val timestamp: Long, - val expiresAt: Long -) - -@Serializable -data class MiningStatsResponse( - val minerAddress: String, - val totalBlocksMined: Int, - val totalRewardEarned: Double, - val lastBlockMined: Long?, - val currentDifficulty: Int -) - -@Serializable -data class BlockRangeResponse( - val blocks: List, - val totalCount: Int, - val fromHeight: Int, - val toHeight: Int -) diff --git a/server/src/main/kotlin/org/ccoin/models/Transaction.kt b/server/src/main/kotlin/org/ccoin/models/Transaction.kt deleted file mode 100644 index 25d2122..0000000 --- a/server/src/main/kotlin/org/ccoin/models/Transaction.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.ccoin.models - -import kotlinx.serialization.Serializable - -@Serializable -data class SendTransactionRequest( - val fromAddress: String, // Format random_word:random_6_digits - val toAddress: String, // Format random_word:random_6_digits - val amount: Double, - val fee: Double = 0.0, - val memo: String? = null, - val password: String -) - -@Serializable -data class TransactionResponse( - val hash: String, - val fromAddress: String?, - val toAddress: String, - val amount: Double, - val fee: Double, - val memo: String?, - val blockHash: String?, - val timestamp: Long, - val status: TransactionStatus, - val confirmations: Int = 0 -) - -@Serializable -data class TransactionHistoryResponse( - val transactions: List, - val totalCount: Int, - val page: Int, - val pageSize: Int -) - -@Serializable -enum class TransactionStatus { - PENDING, - CONFIRMED, - FAILED, - CANCELLED -} diff --git a/server/src/main/kotlin/org/ccoin/models/Wallet.kt b/server/src/main/kotlin/org/ccoin/models/Wallet.kt deleted file mode 100644 index 93b2a0f..0000000 --- a/server/src/main/kotlin/org/ccoin/models/Wallet.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.ccoin.models - -import kotlinx.serialization.Serializable - -@Serializable -data class CreateWalletRequest( - val label: String? = null, - val password: String -) - -@Serializable -data class WalletResponse( - val address: String, // Format random_word:random_6_digits (e.g. "phoenix:123456") - val balance: Double, - val label: String?, - val passwordHash: String, - val createdAt: Long, - val lastActivity: Long? -) - -@Serializable -data class BalanceResponse( - val address: String, - val balance: Double -) - -@Serializable -data class UpdateWalletRequest( - val label: String? -) diff --git a/server/src/main/kotlin/org/ccoin/routes/ApiRoutes.kt b/server/src/main/kotlin/org/ccoin/routes/ApiRoutes.kt deleted file mode 100644 index df122b5..0000000 --- a/server/src/main/kotlin/org/ccoin/routes/ApiRoutes.kt +++ /dev/null @@ -1,208 +0,0 @@ -package org.ccoin.routes - -import io.ktor.http.* -import io.ktor.server.application.* -import org.ccoin.config.ServerConfig -import io.ktor.server.response.* -import io.ktor.server.routing.* -import kotlinx.serialization.Serializable - -fun Route.apiRoutes() { - route("/api") { - - /** Get all available API routes */ - get("/routes") { - try { - val routes = mapOf( - "wallet" to mapOf( - "POST /wallet/create" to "Create a new wallet", - "GET /wallet/{address}" to "Get wallet by address", - "GET /wallet/{address}/balance" to "Get wallet balance", - "PUT /wallet/{address}/label" to "Update wallet label", - "GET /wallet/list" to "Get all wallets with pagination", - "GET /wallet/rich" to "Get wallets with minimum balance", - "GET /wallet/{address}/exists" to "Check if wallet exists" - ), - "transaction" to mapOf( - "POST /transaction/send" to "Send a transaction", - "GET /transaction/{hash}" to "Get transaction by hash", - "GET /transaction/history/{address}" to "Get transaction history for address", - "GET /transaction/pending" to "Get pending transactions", - "GET /transaction/count/{address}" to "Get transaction count for address", - "GET /transaction/list" to "Get all transactions with pagination", - "GET /transaction/stats" to "Get network transaction statistics" - ), - "mining" to mapOf( - "POST /mining/start" to "Start a mining job", - "POST /mining/submit" to "Submit mining result", - "GET /mining/difficulty" to "Get current mining difficulty", - "GET /mining/stats/{address}" to "Get mining statistics for miner", - "GET /mining/network" to "Get network mining statistics", - "GET /mining/pending-transactions" to "Get pending transactions for mining", - "POST /mining/validate" to "Validate mining job", - "GET /mining/leaderboard" to "Get mining leaderboard" - ), - "block" to mapOf( - "GET /block/{hash}" to "Get block by hash", - "GET /block/height/{height}" to "Get block by height", - "GET /block/{hash}/exists" to "Check if block exists", - "GET /blocks/latest" to "Get latest blocks", - "GET /blocks/range" to "Get blocks in height range", - "GET /blocks/miner/{address}" to "Get blocks by miner address", - "GET /blocks/time-range" to "Get blocks by timestamp range", - "GET /blocks/difficulty/{difficulty}" to "Get blocks by difficulty", - "GET /blocks/stats" to "Get blockchain statistics" - ), - "health" to mapOf( - "GET /health" to "Basic health check", - "GET /health/detailed" to "Detailed health check with system metrics", - "GET /health/database" to "Database health check", - "GET /health/blockchain" to "Blockchain health check", - "GET /ready" to "Readiness probe (Kubernetes)", - "GET /live" to "Liveness probe (Kubernetes)", - "GET /version" to "Service version and build info" - ), - "api" to mapOf( - "GET /api/routes" to "Get all available API routes", - "GET /api/docs" to "Get API documentation", - "GET /api/examples" to "Get API usage examples" - ) - ) - - val summary = ApiSummary( - totalEndpoints = routes.values.sumOf { it.size }, - categories = routes.keys.toList(), - baseUrl = ServerConfig.baseUrl, - documentation = "https://github.com/your-repo/ccoin-server/docs", - version = ServerConfig.version - ) - - val response = ApiRoutesResponse(summary, routes) - call.respond(response) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf( - "error" to (e.message ?: "Failed to get routes information") - )) - } - } - - /** Get API documentation */ - get("/docs") { - try { - call.respond(mapOf( - "title" to "CCoin API Documentation", - "version" to ServerConfig.version, - "description" to "REST API for CCoin cryptocurrency server", - "baseUrl" to ServerConfig.baseUrl, - "authentication" to "None required", - "contentType" to "application/json", - "rateLimit" to "100 requests per minute", - "addressFormat" to "random_word:random_6_digits (e.g., phoenix:123456)", - "hashFormat" to "64-character hex string", - "defaultDifficulty" to "4", - "defaultReward" to "50.0", - "memoMaxLength" to "256" - )) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf( - "error" to (e.message ?: "Failed to get API documentation") - )) - } - } - - /** Get API usage examples */ - get("/examples") { - try { - val examples = mapOf( - "createWallet" to ApiExample( - method = "POST", - url = "/wallet/create", - body = """{"label": "My Wallet"}""", - response = """{"address": "phoenix:123456", "balance": 0.0, "label": "My Wallet", "createdAt": 1703097600, "lastActivity": null}""" - ), - "sendTransaction" to ApiExample( - method = "POST", - url = "/transaction/send", - body = """{"fromAddress": "phoenix:123456", "toAddress": "dragon:789012", "amount": 10.5, "fee": 0.01, "memo": "Payment for services"}""", - response = """{"hash": "abc123...", "fromAddress": "phoenix:123456", "toAddress": "dragon:789012", "amount": 10.5, "fee": 0.01, "memo": "Payment for services", "timestamp": 1703097600, "status": "CONFIRMED"}""" - ), - "startMining" to ApiExample( - method = "POST", - url = "/mining/start", - body = """{"minerAddress": "tiger:456789", "difficulty": 4}""", - response = """{"jobId": "job123", "target": "0000", "difficulty": 4, "previousHash": "def456...", "height": 100, "timestamp": 1703097600, "expiresAt": 1703097900}""" - ), - "getBlock" to ApiExample( - method = "GET", - url = "/block/abc123...", - response = """{"hash": "abc123...", "previousHash": "def456...", "merkleRoot": "ghi789...", "timestamp": 1703097600, "difficulty": 4, "nonce": 12345, "minerAddress": "tiger:456789", "reward": 50.0, "height": 100, "transactionCount": 0, "confirmations": 6}""" - ) - ) - - val curlExamples = mapOf( - "createWallet" to """curl -X POST http://localhost:8080/wallet/create -H 'Content-Type: application/json' -d '{"label":"My Wallet"}'""", - "getBalance" to "curl http://localhost:8080/wallet/phoenix:123456/balance", - "sendTransaction" to """curl -X POST http://localhost:8080/transaction/send -H 'Content-Type: application/json' -d '{"fromAddress":"phoenix:123456","toAddress":"dragon:789012","amount":10.5}'""", - "getLatestBlocks" to "curl http://localhost:8080/blocks/latest?limit=5" - ) - - val response = ApiExamplesResponse( - title = "CCoin API Examples", - description = "Common usage examples for the CCoin API", - examples = examples, - curlExamples = curlExamples - ) - - call.respond(response) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf( - "error" to (e.message ?: "Failed to get API examples") - )) - } - } - } -} - -@Serializable -data class ApiSummary( - val totalEndpoints: Int, - val categories: List, - val baseUrl: String, - val documentation: String, - val version: String -) - -@Serializable -data class ApiRoutesResponse( - val summary: ApiSummary, - val routes: Map> -) - -@Serializable -data class ApiExample( - val method: String, - val url: String, - val body: String? = null, - val response: String -) - -@Serializable -data class ApiExamplesResponse( - val title: String, - val description: String, - val examples: Map, - val curlExamples: Map -) - -@Serializable -data class PaginationInfo( - val currentPage: Int, - val pageSize: Int, - val totalItems: Long, - val totalPages: Long, - val hasNext: Boolean, - val hasPrevious: Boolean -) diff --git a/server/src/main/kotlin/org/ccoin/routes/BlockRoutes.kt b/server/src/main/kotlin/org/ccoin/routes/BlockRoutes.kt deleted file mode 100644 index 07a13b0..0000000 --- a/server/src/main/kotlin/org/ccoin/routes/BlockRoutes.kt +++ /dev/null @@ -1,349 +0,0 @@ -package org.ccoin.routes - -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import org.ccoin.services.BlockService -import org.ccoin.services.ValidationService -import org.ccoin.models.BlockResponse -import kotlinx.serialization.Serializable - -fun Route.blockRoutes() { - route("/block") { - /** Get block by hash */ - get("/{hash}") { - try { - val hash = call.parameters["hash"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Block hash parameter required")) - return@get - } - - // Validate hash format - if (!ValidationService.validateBlockHash(hash)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid block hash format")) - return@get - } - - val block = BlockService.getBlock(hash) - if (block != null) { - call.respond(block) - } else { - call.respond(HttpStatusCode.NotFound, mapOf("error" to "Block not found")) - } - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get block"))) - } - } - - /** Get block by height */ - get("/height/{height}") { - try { - val height = call.parameters["height"]?.toIntOrNull() ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Valid height parameter required")) - return@get - } - - if (height < 0) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Height must be non-negative")) - return@get - } - - val block = BlockService.getBlockByHeight(height) - if (block != null) { - call.respond(block) - } else { - call.respond(HttpStatusCode.NotFound, mapOf("error" to "Block not found at height $height")) - } - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get block by height"))) - } - } - - /** Check if block exists */ - get("/{hash}/exists") { - try { - val hash = call.parameters["hash"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Block hash parameter required")) - return@get - } - - // Validate hash format - if (!ValidationService.validateBlockHash(hash)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid block hash format")) - return@get - } - - val exists = BlockService.blockExists(hash) - call.respond( - BlockExistsResponse( - hash = hash, - exists = exists - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to check block existence"))) - } - } - } - - route("/blocks") { - - /** Get latest blocks */ - get("/latest") { - try { - val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 10 - - if (limit !in 1..100) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Limit must be between 1 and 100")) - return@get - } - - val blocks = BlockService.getLatestBlocks(limit) - call.respond( - BlocksLatestResponse( - blocks = blocks, - count = blocks.size - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get latest blocks"))) - } - } - - /** Get blocks in height range */ - get("/range") { - try { - val fromHeight = call.request.queryParameters["from"]?.toIntOrNull() ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Valid 'from' height parameter required")) - return@get - } - - val toHeight = call.request.queryParameters["to"]?.toIntOrNull() ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Valid 'to' height parameter required")) - return@get - } - - if (fromHeight < 0 || toHeight < 0) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Heights must be non-negative")) - return@get - } - - if (fromHeight > toHeight) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "From height must be less than or equal to to height")) - return@get - } - - if (toHeight - fromHeight > 1000) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Range too large (max 1000 blocks)")) - return@get - } - - val blockRange = BlockService.getBlocksInRange(fromHeight, toHeight) - call.respond(blockRange) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get blocks in range"))) - } - } - - /** Get blocks by miner address */ - get("/miner/{address}") { - try { - val address = call.parameters["address"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Address parameter required")) - return@get - } - - val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 - val pageSize = call.request.queryParameters["pageSize"]?.toIntOrNull() ?: 50 - - // Validate address format - if (!ValidationService.validateWalletAddress(address)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid address format")) - return@get - } - - // Validate pagination - val validation = ValidationService.validatePagination(page, pageSize) - if (!validation.isValid) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to validation.getErrorMessage())) - return@get - } - - val offset = (page - 1) * pageSize - val blocks = BlockService.getBlocksByMiner(address, pageSize, offset) - - // call.respond(mapOf( - // "blocks" to blocks, - // "minerAddress" to address, - // "pagination" to mapOf( - // "currentPage" to page, - // "pageSize" to pageSize, - // "hasNext" to (blocks.size == pageSize), - // "hasPrevious" to (page > 1) - // ) - // )) - call.respond( - BlocksMinerResponse( - blocks = blocks, - minerAddress = address, - pagination = PaginationInfo( - currentPage = page, - pageSize = pageSize, - hasNext = (blocks.size == pageSize), - hasPrevious = (page > 1), - totalPages = blocks.size.toLong(), - totalItems = blocks.size.toLong() - ) - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get blocks by miner"))) - } - } - - /** Get blocks by timestamp range */ - get("/time-range") { - try { - val fromTime = call.request.queryParameters["from"]?.toLongOrNull() ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Valid 'from' timestamp parameter required")) - return@get - } - - val toTime = call.request.queryParameters["to"]?.toLongOrNull() ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Valid 'to' timestamp parameter required")) - return@get - } - - if (fromTime < 0 || toTime < 0) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Timestamps must be non-negative")) - return@get - } - - if (fromTime > toTime) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "From time must be less than or equal to to time")) - return@get - } - - // Limit to 30 days max - if (toTime - fromTime > 30 * 24 * 60 * 60) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Time range too large (max 30 days)")) - return@get - } - - val blocks = BlockService.getBlocksByTimeRange(fromTime, toTime) - call.respond( - BlocksTimeRangeResponse( - blocks = blocks, - fromTime = fromTime, - toTime = toTime, - count = blocks.size - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get blocks by time range"))) - } - } - - /** Get blocks by difficulty */ - get("/difficulty/{difficulty}") { - try { - val difficulty = call.parameters["difficulty"]?.toIntOrNull() ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Valid difficulty parameter required")) - return@get - } - - if (!ValidationService.validateMiningDifficulty(difficulty)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid mining difficulty")) - return@get - } - - val blocks = BlockService.getBlocksByDifficulty(difficulty) - call.respond( - BlocksDifficultyResponse( - blocks = blocks, - difficulty = difficulty, - count = blocks.size - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get blocks by difficulty"))) - } - } - - /** Get blockchain statistics */ - get("/stats") { - try { - val totalBlocks = BlockService.getTotalBlockCount() - val latestHeight = BlockService.getLatestBlockHeight() - val latestHash = BlockService.getLatestBlockHash() - val averageBlockTime = BlockService.getAverageBlockTime() - val totalRewards = BlockService.getTotalRewardsDistributed() - - call.respond( - BlocksStatsResponse( - totalBlocks = totalBlocks, - latestHeight = latestHeight, - latestHash = latestHash, - averageBlockTime = averageBlockTime, - totalRewardsDistributed = totalRewards - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get blockchain statistics"))) - } - } - } -} - -@Serializable -data class BlockExistsResponse( - val hash: String, - val exists: Boolean -) - -@Serializable -data class BlocksLatestResponse( - val blocks: List, - val count: Int -) - -@Serializable -data class BlocksMinerResponse( - val blocks: List, - val minerAddress: String, - val pagination: PaginationInfo -) - -@Serializable -data class BlocksTimeRangeResponse( - val blocks: List, - val fromTime: Long, - val toTime: Long, - val count: Int -) - -@Serializable -data class BlocksDifficultyResponse( - val blocks: List, - val difficulty: Int, - val count: Int -) - -@Serializable -data class BlocksStatsResponse( - val totalBlocks: Long, - val latestHeight: Int, - val latestHash: String, - val averageBlockTime: Long, - val totalRewardsDistributed: Double -) diff --git a/server/src/main/kotlin/org/ccoin/routes/HealthRoutes.kt b/server/src/main/kotlin/org/ccoin/routes/HealthRoutes.kt deleted file mode 100644 index e773db4..0000000 --- a/server/src/main/kotlin/org/ccoin/routes/HealthRoutes.kt +++ /dev/null @@ -1,315 +0,0 @@ -package org.ccoin.routes - -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import org.ccoin.config.DatabaseConfig -import org.ccoin.config.ServerConfig -import org.ccoin.models.DatabaseHealth -import org.ccoin.models.BlockchainHealth -import org.ccoin.models.HealthResponse -import org.ccoin.services.BlockService -import org.ccoin.services.TransactionService -import org.ccoin.services.WalletService -import org.ccoin.services.MiningService -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.transactions.transaction -import java.lang.management.ManagementFactory -import kotlinx.serialization.Serializable - -fun Route.healthRoutes() { - /** Basic health check */ - get("/health") { - try { - val startTime = System.currentTimeMillis() - - // Check database connectivity - val dbHealth = checkDatabaseHealth() - - // Check blockchain health - val blockchainHealth = checkBlockchainHealth() - - val uptime = ManagementFactory.getRuntimeMXBean().uptime - - val health = HealthResponse( - status = if (dbHealth.connected) "healthy" else "unhealthy", - version = ServerConfig.version, - uptime = uptime, - database = dbHealth, - blockchain = blockchainHealth - ) - - val statusCode = if (dbHealth.connected) HttpStatusCode.OK else HttpStatusCode.ServiceUnavailable - call.respond(statusCode, health) - - } catch (e: Exception) { - call.respond(HttpStatusCode.ServiceUnavailable, mapOf( - "status" to "unhealthy", - "error" to (e.message ?: "Health check failed") - )) - } - } - - /** Detailed health check */ - get("/health/detailed") { - try { - val dbHealth = checkDatabaseHealth() - val blockchainHealth = checkBlockchainHealth() - - // Additional checks - val memoryUsage = getMemoryUsage() - val diskSpace = getDiskSpace() - val networkStats = getNetworkStats() - - call.respond(HealthDetailedResponse( - status = if (dbHealth.connected) "healthy" else "unhealthy", - version = ServerConfig.version, - uptime = ManagementFactory.getRuntimeMXBean().uptime, - timestamp = System.currentTimeMillis(), - database = dbHealth, - blockchain = blockchainHealth, - system = SystemInfo(memoryUsage, diskSpace), - network = networkStats, - )) - - } catch (e: Exception) { - call.respond(HttpStatusCode.ServiceUnavailable, mapOf( - "status" to "unhealthy", - "error" to (e.message ?: "Detailed health check failed") - )) - } - } - - /** Database health check */ - get("/health/database") { - try { - val dbHealth = checkDatabaseHealth() - val statusCode = if (dbHealth.connected) HttpStatusCode.OK else HttpStatusCode.ServiceUnavailable - call.respond(statusCode, dbHealth) - - } catch (e: Exception) { - call.respond(HttpStatusCode.ServiceUnavailable, mapOf( - "connected" to false, - "error" to (e.message ?: "Database health check failed") - )) - } - } - - /** Blockchain health check */ - get("/health/blockchain") { - try { - val blockchainHealth = checkBlockchainHealth() - call.respond(blockchainHealth) - - } catch (e: Exception) { - call.respond(HttpStatusCode.ServiceUnavailable, mapOf( - "error" to (e.message ?: "Blockchain health check failed") - )) - } - } - - /** Readiness probe (for Kubernetes) */ - get("/ready") { - try { - val dbHealth = checkDatabaseHealth() - - if (dbHealth.connected) { - call.respond(HttpStatusCode.OK, mapOf("status" to "ready")) - } else { - call.respond(HttpStatusCode.ServiceUnavailable, mapOf("status" to "not ready")) - } - - } catch (e: Exception) { - call.respond(HttpStatusCode.ServiceUnavailable, mapOf( - "status" to "not ready", - "error" to (e.message ?: "Readiness check failed") - )) - } - } - - /** Liveness probe (for Kubernetes) */ - get("/live") { - try { - // Simple liveness check - just return OK if the service is running - call.respond(HttpStatusCode.OK, mapOf("status" to "alive")) - - } catch (e: Exception) { - call.respond(HttpStatusCode.ServiceUnavailable, mapOf( - "status" to "dead", - "error" to (e.message ?: "Liveness check failed") - )) - } - } - - /** Service version and build info */ - get("/version") { - try { - call.respond(mapOf( - "version" to ServerConfig.version, - "buildTime" to ServerConfig.buildTime, - "gitCommit" to System.getProperty("git.commit", "unknown"), - "javaVersion" to System.getProperty("java.version"), - // "kotlinVersion" to System.getProperty("kotlin_version").toString() - )) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf( - "error" to (e.message ?: "Version check failed") - )) - } - } -} - -/** Check database health */ -private fun checkDatabaseHealth(): DatabaseHealth { - return try { - val startTime = System.currentTimeMillis() - - transaction { - // Simple query to test connectivity - WalletService.getTotalWalletCount() - } - - val responseTime = System.currentTimeMillis() - startTime - - DatabaseHealth( - connected = true, - responseTime = responseTime, - activeConnections = 0, // Would need HikariCP integration to get real numbers - maxConnections = 20 - ) - - } catch (e: Exception) { - DatabaseHealth( - connected = false, - responseTime = -1, - activeConnections = 0, - maxConnections = 20 - ) - } -} - -/** Check blockchain health */ -private fun checkBlockchainHealth(): BlockchainHealth { - return try { - val latestBlock = BlockService.getLatestBlockHeight() - val pendingTransactions = TransactionService.getPendingTransactions().size - val networkHashRate = MiningService.getNetworkHashRate() - val averageBlockTime = BlockService.getAverageBlockTime() - - BlockchainHealth( - latestBlock = latestBlock, - pendingTransactions = pendingTransactions, - networkHashRate = networkHashRate, - averageBlockTime = averageBlockTime - ) - - } catch (e: Exception) { - BlockchainHealth( - latestBlock = 0, - pendingTransactions = 0, - networkHashRate = 0.0, - averageBlockTime = 0L - ) - } -} - -/** Get memory usage information */ -private fun getMemoryUsage(): MemoryInfo { - val runtime = Runtime.getRuntime() - val maxMemory = runtime.maxMemory() - val totalMemory = runtime.totalMemory() - val freeMemory = runtime.freeMemory() - val usedMemory = totalMemory - freeMemory - - return MemoryInfo( - maxMemory = maxMemory, - totalMemory = totalMemory, - usedMemory = usedMemory, - freeMemory = freeMemory, - usagePercentage = ((usedMemory.toDouble() / maxMemory) * 100).toInt() - ) -} - -/** Get disk space information */ -private fun getDiskSpace(): DiskInfo { - return try { - val file = java.io.File(".") - val totalSpace = file.totalSpace - val freeSpace = file.freeSpace - val usedSpace = totalSpace - freeSpace - - DiskInfo( - totalSpace = totalSpace, - freeSpace = freeSpace, - usedSpace = usedSpace, - usagePercentage = ((usedSpace.toDouble() / totalSpace) * 100).toInt() - ) - } catch (e: Exception) { - DiskInfo(0,0,0,0) - } -} - -/** Get network statistics */ -private fun getNetworkStats(): NetworkStats { - return try { - val totalBlocks = BlockService.getTotalBlockCount() - val totalTransactions = TransactionService.getTotalTransactionCount() - val totalWallets = WalletService.getTotalWalletCount() - val totalSupply = WalletService.getTotalSupply() - - NetworkStats( - totalBlocks = totalBlocks, - totalTransactions = totalTransactions, - totalWallets = totalWallets, - totalSupply = totalSupply - ) - } catch (e: Exception) { - NetworkStats(0,0,0,0.0) - } -} - -@Serializable -data class HealthDetailedResponse( - val status: String, - val version: String, - val uptime: Long, - val timestamp: Long, - val database: DatabaseHealth, - val blockchain: BlockchainHealth, - val system: SystemInfo, - val network: NetworkStats, - // val config: Map -) - -@Serializable -data class MemoryInfo( - val maxMemory: Long, - val totalMemory: Long, - val usedMemory: Long, - val freeMemory: Long, - val usagePercentage: Int -) - -@Serializable -data class DiskInfo( - val totalSpace: Long, - val freeSpace: Long, - val usedSpace: Long, - val usagePercentage: Int -) - -@Serializable -data class NetworkStats( - val totalBlocks: Long, - val totalTransactions: Long, - val totalWallets: Long, - val totalSupply: Double -) - -@Serializable -data class SystemInfo( - val memory: MemoryInfo, - val disk: DiskInfo -) diff --git a/server/src/main/kotlin/org/ccoin/routes/MiningRoutes.kt b/server/src/main/kotlin/org/ccoin/routes/MiningRoutes.kt deleted file mode 100644 index 7b2bcba..0000000 --- a/server/src/main/kotlin/org/ccoin/routes/MiningRoutes.kt +++ /dev/null @@ -1,266 +0,0 @@ -package org.ccoin.routes - -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import org.ccoin.models.StartMiningRequest -import org.ccoin.models.SubmitMiningRequest -import org.ccoin.services.MiningService -import org.ccoin.services.ValidationService -import kotlinx.serialization.Serializable - -fun Route.miningRoutes() { - route("/mining") { - /** Start a mining job */ - post("/start") { - try { - val request = call.receive() - - // Validate miner address - if (!ValidationService.validateWalletAddress(request.minerAddress)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid miner address format")) - return@post - } - - // Validate difficulty if provided - if (request.difficulty != null && !ValidationService.validateMiningDifficulty(request.difficulty)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid mining difficulty")) - return@post - } - - val job = MiningService.startMining(request.minerAddress, request.difficulty) - call.respond(HttpStatusCode.Created, job) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to start mining job"))) - } - } - - /** Sumit mining result */ - post("/submit") { - try { - val request = call.receive() - - // Validate miner address - if (!ValidationService.validateWalletAddress(request.minerAddress)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid miner address format")) - return@post - } - - // Validate hash format - if (!ValidationService.validateBlockHash(request.hash)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid hash format")) - return@post - } - - // Validate previous hash format - if (!ValidationService.validateBlockHash(request.previousHash)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid previous hash format")) - return@post - } - - // Validate nonce - if (!ValidationService.validateMiningNonce(request.nonce)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid nonce")) - return@post - } - - // Validate timestamp - if (!ValidationService.validateTimestamp(request.timestamp)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid timestamp")) - return@post - } - - val block = MiningService.submitMiningResult( - request.minerAddress, - request.nonce, - request.hash, - request.previousHash, - request.timestamp - ) - - call.respond(HttpStatusCode.Created, block) - - } catch (e: Exception) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to (e.message ?: "Mining submission failed"))) - } - } - - /** Get current mining difficulty */ - get("/difficulty") { - try { - val difficulty = MiningService.getCurrentDifficulty() - call.respond(mapOf("difficulty" to difficulty)) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get difficulty"))) - } - } - - /** Get mining statistics for a miner */ - get("/stats/{address}") { - try { - val address = call.parameters["address"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Address parameter required")) - return@get - } - - // Validate address format - if (!ValidationService.validateWalletAddress(address)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid address format")) - return@get - } - - val stats = MiningService.getMinerStats(address) - call.respond(stats) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get miner statistics"))) - } - } - - /** Get network mining statistics */ - get("/network") { - try { - val hashRate = MiningService.getNetworkHashRate() - val averageBlockTime = MiningService.getAverageBlockTime() - val activeMiners = MiningService.getActiveMinersCount() - val difficulty = MiningService.getCurrentDifficulty() - - call.respond( - MiningNetworkResponse( - networkHashRate = hashRate, - averageBlockTime = averageBlockTime, - activeMiners = activeMiners, - currentDifficulty = difficulty - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get network statistics"))) - } - } - - /** Get pending transactions available for mining */ - get("/pending-transactions") { - try { - val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100 - - if (limit !in 1..1000) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Limit must be between 1 and 1000")) - return@get - } - - val transactions = MiningService.getPendingTransactionsForMining(limit) - call.respond( - MiningPendingTransactionsResponse( - transactions = transactions, - count = transactions.size - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get pending transactions"))) - } - } - - /** Validate mining job */ - post("/validate") { - try { - val jobId = call.request.queryParameters["jobId"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Job ID parameter required")) - return@post - } - - val hash = call.request.queryParameters["hash"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Hash parameter required")) - return@post - } - - val nonce = call.request.queryParameters["nonce"]?.toLongOrNull() ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Valid nonce parameter required")) - return@post - } - - // Validate hash format - if (!ValidationService.validateBlockHash(hash)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid hash format")) - return@post - } - - // Validate nonce - if (!ValidationService.validateMiningNonce(nonce)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid nonce")) - return@post - } - - val isValid = MiningService.validateMiningJob(jobId, hash, nonce) - call.respond( - MiningValidateResponse( - jobId = jobId, - hash = hash, - nonce = nonce, - isValid = isValid - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to validate mining job"))) - } - } - - /** Get mining leaderboard */ - get("/leaderboard") { - try { - val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 10 - - if (limit !in 0..100) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Limit must be between 1 and 100")) - return@get - } - - // This would need a new service method to get top miners - // For now, return a placeholder response - call.respond( - MiningLeaderboardResponse( - message = "Not implemented", - limit = limit - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get mining leaderboard"))) - } - } - } -} - -@Serializable -data class MiningNetworkResponse( - val networkHashRate: Double, - val averageBlockTime: Long, - val activeMiners: Int, - val currentDifficulty: Int -) - -@Serializable -data class MiningLeaderboardResponse( - val message: String, - val limit: Int -) - -@Serializable -data class MiningValidateResponse( - val jobId: String, - val hash: String, - val nonce: Long, - val isValid: Boolean -) - -@Serializable -data class MiningPendingTransactionsResponse( - val transactions: List, - val count: Int -) diff --git a/server/src/main/kotlin/org/ccoin/routes/TransactionRoutes.kt b/server/src/main/kotlin/org/ccoin/routes/TransactionRoutes.kt deleted file mode 100644 index effeb5d..0000000 --- a/server/src/main/kotlin/org/ccoin/routes/TransactionRoutes.kt +++ /dev/null @@ -1,242 +0,0 @@ -package org.ccoin.routes - -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import org.ccoin.models.SendTransactionRequest -import org.ccoin.models.TransactionResponse -import org.ccoin.services.TransactionService -import org.ccoin.services.ValidationService -import kotlinx.serialization.Serializable -import org.ccoin.routes.PaginationInfo - -fun Route.transactionRoutes() { - route("/transaction") { - /** Send a transaction */ - post("/send") { - try { - val request = call.receive() - - // Validate transaction data - val validation = ValidationService.validateTransaction( - request.fromAddress, - request.toAddress, - request.amount, - request.password, - request.fee, - request.memo - ) - - if (!validation.isValid) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to validation.getErrorMessage())) - return@post - } - - val transaction = TransactionService.sendTransaction( - request.fromAddress, - request.toAddress, - request.amount, - request.password, - request.fee, - request.memo, - ) - - call.respond(HttpStatusCode.Created, transaction) - - } catch (e: Exception) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to (e.message ?: "Transaction failed"))) - } - } - - /** Get transaction by hash */ - get("/{hash}") { - try { - val hash = call.parameters["hash"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Transaction hash parameter required")) - return@get - } - - // Validate hash format - if (!ValidationService.validateTransactionHash(hash)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid transaction hash format")) - return@get - } - - val transaction = TransactionService.getTransaction(hash) - if (transaction != null) { - call.respond(transaction) - } else { - call.respond(HttpStatusCode.NotFound, mapOf("error" to "Transaction not found")) - } - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get transaction"))) - } - } - - /** Get transaction history for an address */ - get("/history/{address}") { - try { - val address = call.parameters["address"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Address parameter required")) - return@get - } - - val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 - val pageSize = call.request.queryParameters["pageSize"]?.toIntOrNull() ?: 50 - - // Validate address format - if (!ValidationService.validateWalletAddress(address)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid address format")) - return@get - } - - // Validate pagination - val validation = ValidationService.validatePagination(page, pageSize) - if (!validation.isValid) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to validation.getErrorMessage())) - return@get - } - - val offset = (page - 1) * pageSize - val transactions = TransactionService.getTransactionHistory(address, pageSize, offset) - val totalCount = TransactionService.getTransactionCountForAddress(address) - - call.respond( - TransactionHistoryResponse( - transactions = transactions, - address = address, - pagination = PaginationInfo( - currentPage = page, - pageSize = pageSize, - totalItems = totalCount, - totalPages = ((totalCount + pageSize - 1) / pageSize), - hasNext = (offset + pageSize < totalCount), - hasPrevious = (page > 1) - ) - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get transaction history"))) - } - } - - /** Get pending transactions */ - get("/pending") { - try { - val transactions = TransactionService.getPendingTransactions() - - call.respond( - TransactionPendingResponse( - transactions = transactions, - count = transactions.size - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get pending transactions"))) - } - } - - /** Get transaction count for address */ - get("/count/{address}") { - try { - val address = call.parameters["address"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Address parameter required")) - return@get - } - - // Validate address format - if (!ValidationService.validateWalletAddress(address)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid address format")) - return@get - } - - val count = TransactionService.getTransactionCountForAddress(address) - call.respond(mapOf( - "address" to address, - "transactionCount" to count - )) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get transaction count"))) - } - } - - /** Get all transactions with pagination */ - get("/list") { - try { - val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 - val pageSize = call.request.queryParameters["pageSize"]?.toIntOrNull() ?: 50 - val status = call.request.queryParameters["status"] // Optional filter - - // Validate pagination - val validation = ValidationService.validatePagination(page, pageSize) - if (!validation.isValid) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to validation.getErrorMessage())) - return@get - } - - // For now, just get pending transactions as an example - // You could extend this to support status filtering - val transactions = if (status == "pending") { - TransactionService.getPendingTransactions() - } else { - // This would need a new service method for all transactions - TransactionService.getPendingTransactions() // Placeholder - } - - call.respond(mapOf( - "transactions" to transactions, - "count" to transactions.size, - "status" to status - )) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get transactions"))) - } - } - - /** Get network transaction statistics */ - get("/stats") { - try { - val totalCount = TransactionService.getTotalTransactionCount() - val pendingCount = TransactionService.getPendingTransactions().size - - call.respond( - TransactionStatsResponse( - totalTransactions = totalCount, - pendingTransactions = pendingCount, - confirmedTransactions = (totalCount - pendingCount) - ) - ) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get transaction statistics"))) - } - } - } -} - -@Serializable -data class TransactionHistoryResponse( - val transactions: List, - val address: String, - val pagination: PaginationInfo -) - -@Serializable -data class TransactionPendingResponse( - val transactions: List, - val count: Int -) - -@Serializable -data class TransactionStatsResponse( - val totalTransactions: Long, - val pendingTransactions: Int, - val confirmedTransactions: Long -) diff --git a/server/src/main/kotlin/org/ccoin/routes/WalletRoutes.kt b/server/src/main/kotlin/org/ccoin/routes/WalletRoutes.kt deleted file mode 100644 index bf49873..0000000 --- a/server/src/main/kotlin/org/ccoin/routes/WalletRoutes.kt +++ /dev/null @@ -1,214 +0,0 @@ -package org.ccoin.routes - -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import org.ccoin.models.CreateWalletRequest -import org.ccoin.models.UpdateWalletRequest -import org.ccoin.models.WalletResponse -import org.ccoin.services.ValidationService -import org.ccoin.services.WalletService -import kotlinx.serialization.Serializable -import org.ccoin.routes.PaginationInfo - -fun Route.walletRoutes() { - route("/wallet") { - /** Create a new wallet */ - post("/create") { - try { - val request = call.receive() - - // Validate input - val validation = ValidationService.validateWalletCreation(request.label) - if (!validation.isValid) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to validation.getErrorMessage())) - return@post - } - - val wallet = WalletService.createWallet(request.label, request.password) - call.respond(HttpStatusCode.Created, wallet) - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to create wallet"))) - } - } - - /** Get wallet by address */ - get("/{address}") { - try { - val address = call.parameters["address"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Address parameter required")) - return@get - } - - // Validate address format - if (!ValidationService.validateWalletAddress(address)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid adress format")) - return@get - } - - val wallet = WalletService.getWallet(address) - if (wallet != null) { - call.respond(wallet) - } else { - call.respond(HttpStatusCode.NotFound, mapOf("error" to "Wallet not found")) - } - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get wallet"))) - } - } - - /** Get wallet balance */ - get("/{address}/balance") { - try { - val address = call.parameters["address"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Address parameter required")) - return@get - } - - // Validate address format - if (!ValidationService.validateWalletAddress(address)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid address format")) - return@get - } - - val balance = WalletService.getWalletBalance(address) - call.respond(WalletBalanceResponse(address, balance)) - - } catch (e: Exception) { - call.respond(HttpStatusCode.NotFound, mapOf("error" to (e.message ?: "Wallet not found"))) - } - } - - /** Update wallet label */ - put("/{address}/label") { - try { - val address = call.parameters["address"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Address parameter required")) - return@put - } - - val request = call.receive() - - // Validate address format - if (!ValidationService.validateWalletAddress(address)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid address format")) - return@put - } - - // Validate label - if (!ValidationService.validateWalletLabel(request.label)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid label")) - return@put - } - - val updated = WalletService.updateLabel(address, request.label) - if (updated) { - call.respond(mapOf("message" to "Label updated successfully")) - } else { - call.respond(HttpStatusCode.NotFound, mapOf("error" to "Wallet not found")) - } - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to update label"))) - } - } - - /** Get all wallets with pagination */ - get("/list") { - try { - val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 - val pageSize = call.request.queryParameters["pageSize"]?.toIntOrNull() ?: 50 - - // Validate pagination - val validation = ValidationService.validatePagination(page, pageSize) - if (!validation.isValid) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to validation.getErrorMessage())) - return@get - } - - val offset = (page - 1) * pageSize - val wallets = WalletService.getAllWallets(pageSize, offset) - val totalCount = WalletService.getTotalWalletCount() - val paginationInfo = PaginationInfo( - currentPage = page, - pageSize = pageSize, - totalItems = totalCount, - totalPages = (totalCount + pageSize - 1) / pageSize, - hasNext = offset + pageSize < totalCount, - hasPrevious = page > 1 - ) - - call.respond(WalletListResponse(wallets, paginationInfo)) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get wallets"))) - } - } - - /** Get wallets with minimum balance */ - get("/rich") { - try { - val minBalance = call.request.queryParameters["minBalance"]?.toDoubleOrNull() ?: 1.0 - - if (!ValidationService.validateTransactionAmount(minBalance)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid minimum balance")) - return@get - } - - val wallets = WalletService.getWalletsWithBalance(minBalance) - call.respond(WalletRichResponse(wallets, minBalance)) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get rich wallets"))) - } - } - - /** Check if wallet exists */ - get("/{address}/exists") { - try { - val address = call.parameters["address"] ?: run { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Address parameter required")) - return@get - } - - // Validate address format - if (!ValidationService.validateWalletAddress(address)) { - call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Invalid address format")) - return@get - } - - val exists = WalletService.walletExists(address) - call.respond(WalletExistsResponse(address, exists)) - - } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to check wallet existence"))) - } - } - } -} - -@Serializable -data class WalletListResponse( - val wallets: List, - val pagination: PaginationInfo -) - -@Serializable -data class WalletRichResponse( - val wallets: List, - val minBalance: Double -) - -@Serializable -data class WalletExistsResponse( - val address: String, - val exists: Boolean -) - -@Serializable -data class WalletBalanceResponse( - val address: String, - val balance: Double -) diff --git a/server/src/main/kotlin/org/ccoin/services/BlockService.kt b/server/src/main/kotlin/org/ccoin/services/BlockService.kt deleted file mode 100644 index 26c8352..0000000 --- a/server/src/main/kotlin/org/ccoin/services/BlockService.kt +++ /dev/null @@ -1,218 +0,0 @@ -package org.ccoin.services - -import org.ccoin.database.Tables -import org.ccoin.models.BlockResponse -import org.ccoin.models.BlockRangeResponse -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction - -object BlockService { - /** Gets block by hash */ - fun getBlock(hash: String): BlockResponse? = transaction { - Tables.Blocks.selectAll().where { Tables.Blocks.hash eq hash } - .map { - BlockResponse( - it[Tables.Blocks.hash], - it[Tables.Blocks.previousHash], - it[Tables.Blocks.merkleRoot], - it[Tables.Blocks.timestamp], - it[Tables.Blocks.difficulty], - it[Tables.Blocks.nonce], - it[Tables.Blocks.minerAddress], - it[Tables.Blocks.reward].toDouble(), - it[Tables.Blocks.height], - it[Tables.Blocks.transactionCount], - it[Tables.Blocks.confirmations] - ) - }.singleOrNull() - } - - /** Gets block by height */ - fun getBlockByHeight(height: Int): BlockResponse? = transaction { - Tables.Blocks.selectAll().where { Tables.Blocks.height eq height } - .map { - BlockResponse( - it[Tables.Blocks.hash], - it[Tables.Blocks.previousHash], - it[Tables.Blocks.merkleRoot], - it[Tables.Blocks.timestamp], - it[Tables.Blocks.difficulty], - it[Tables.Blocks.nonce], - it[Tables.Blocks.minerAddress], - it[Tables.Blocks.reward].toDouble(), - it[Tables.Blocks.height], - it[Tables.Blocks.transactionCount], - it[Tables.Blocks.confirmations] - ) - }.singleOrNull() - } - - /** Gets latest blocks */ - fun getLatestBlocks(limit: Int = 10): List = transaction { - Tables.Blocks.selectAll() - .orderBy(Tables.Blocks.height, SortOrder.DESC) - .limit(limit) - .map { - BlockResponse( - it[Tables.Blocks.hash], - it[Tables.Blocks.previousHash], - it[Tables.Blocks.merkleRoot], - it[Tables.Blocks.timestamp], - it[Tables.Blocks.difficulty], - it[Tables.Blocks.nonce], - it[Tables.Blocks.minerAddress], - it[Tables.Blocks.reward].toDouble(), - it[Tables.Blocks.height], - it[Tables.Blocks.transactionCount], - it[Tables.Blocks.confirmations] - ) - } - } - - /** Gets blocks in height range */ - fun getBlocksInRange(fromHeight: Int, toHeight: Int): BlockRangeResponse = transaction { - val blocks = Tables.Blocks.selectAll() - .where { Tables.Blocks.height.between(fromHeight, toHeight) } - .orderBy(Tables.Blocks.height, SortOrder.ASC) - .map { - BlockResponse( - it[Tables.Blocks.hash], - it[Tables.Blocks.previousHash], - it[Tables.Blocks.merkleRoot], - it[Tables.Blocks.timestamp], - it[Tables.Blocks.difficulty], - it[Tables.Blocks.nonce], - it[Tables.Blocks.minerAddress], - it[Tables.Blocks.reward].toDouble(), - it[Tables.Blocks.height], - it[Tables.Blocks.transactionCount], - it[Tables.Blocks.confirmations] - ) - } - - BlockRangeResponse(blocks, blocks.size, fromHeight, toHeight) - } - - /** Gets blocks mined by specific address */ - fun getBlocksByMiner(minerAddress: String, limit: Int = 50, offset: Int = 0): List = transaction { - Tables.Blocks.selectAll() - .where { Tables.Blocks.minerAddress eq minerAddress } - .orderBy(Tables.Blocks.height, SortOrder.DESC) - .limit(limit) - .offset(offset.toLong()) - .map { - BlockResponse( - it[Tables.Blocks.hash], - it[Tables.Blocks.previousHash], - it[Tables.Blocks.merkleRoot], - it[Tables.Blocks.timestamp], - it[Tables.Blocks.difficulty], - it[Tables.Blocks.nonce], - it[Tables.Blocks.minerAddress], - it[Tables.Blocks.reward].toDouble(), - it[Tables.Blocks.height], - it[Tables.Blocks.transactionCount], - it[Tables.Blocks.confirmations] - ) - } - } - - /** Gets total block count */ - fun getTotalBlockCount(): Long = transaction { - Tables.Blocks.selectAll().count() - } - - /** Gets latest block height */ - fun getLatestBlockHeight(): Int = transaction { - Tables.Blocks.selectAll() - .orderBy(Tables.Blocks.height, SortOrder.DESC) - .limit(1) - .map { it[Tables.Blocks.height] } - .singleOrNull() ?: 0 - } - - /** Gets latest block hash */ - fun getLatestBlockHash(): String = transaction { - Tables.Blocks.selectAll() - .orderBy(Tables.Blocks.height, SortOrder.DESC) - .limit(1) - .map { it[Tables.Blocks.hash] } - .singleOrNull() ?: "0".repeat(64) - } - - /** Checks if block exists */ - fun blockExists(hash: String): Boolean = transaction { - Tables.Blocks.selectAll().where { Tables.Blocks.hash eq hash }.count() > 0 - } - - /** Gets blocks by timestamp range */ - fun getBlocksByTimeRange(fromTime: Long, toTime: Long): List = transaction { - Tables.Blocks.selectAll() - .where { Tables.Blocks.timestamp.between(fromTime, toTime) } - .orderBy(Tables.Blocks.timestamp, SortOrder.ASC) - .map { - BlockResponse( - it[Tables.Blocks.hash], - it[Tables.Blocks.previousHash], - it[Tables.Blocks.merkleRoot], - it[Tables.Blocks.timestamp], - it[Tables.Blocks.difficulty], - it[Tables.Blocks.nonce], - it[Tables.Blocks.minerAddress], - it[Tables.Blocks.reward].toDouble(), - it[Tables.Blocks.height], - it[Tables.Blocks.transactionCount], - it[Tables.Blocks.confirmations] - ) - } - } - - /** Updates block confirmations */ - fun updateBlockConfirmations(hash: String, confirmations: Int): Boolean = transaction { - val updated = Tables.Blocks.update({ Tables.Blocks.hash eq hash }) { - it[Tables.Blocks.confirmations] = confirmations - } - updated > 0 - } - - /** Gets average block time over last N blocks */ - fun getAverageBlockTime(blockCount: Int = 100): Long = transaction { - val blocks = Tables.Blocks.selectAll() - .orderBy(Tables.Blocks.timestamp, SortOrder.DESC) - .limit(blockCount) - .map { it[Tables.Blocks.timestamp] } - - if (blocks.size < 2) return@transaction 0L - - val timeDiffs = blocks.zipWithNext { newer, older -> newer - older } - timeDiffs.average().toLong() - } - - /** Gets total rewards distributed */ - fun getTotalRewardsDistributed(): Double = transaction { - Tables.Blocks.select(Tables.Blocks.reward.sum()) - .single()[Tables.Blocks.reward.sum()]?.toDouble() ?: 0.0 - } - - /** Gets blocks with specific difficulty */ - fun getBlocksByDifficulty(difficulty: Int): List = transaction { - Tables.Blocks.selectAll() - .where { Tables.Blocks.difficulty eq difficulty } - .orderBy(Tables.Blocks.height, SortOrder.DESC) - .map { - BlockResponse( - it[Tables.Blocks.hash], - it[Tables.Blocks.previousHash], - it[Tables.Blocks.merkleRoot], - it[Tables.Blocks.timestamp], - it[Tables.Blocks.difficulty], - it[Tables.Blocks.nonce], - it[Tables.Blocks.minerAddress], - it[Tables.Blocks.reward].toDouble(), - it[Tables.Blocks.height], - it[Tables.Blocks.transactionCount], - it[Tables.Blocks.confirmations] - ) - } - } -} diff --git a/server/src/main/kotlin/org/ccoin/services/MiningService.kt b/server/src/main/kotlin/org/ccoin/services/MiningService.kt deleted file mode 100644 index 925fb19..0000000 --- a/server/src/main/kotlin/org/ccoin/services/MiningService.kt +++ /dev/null @@ -1,185 +0,0 @@ -package org.ccoin.services - -import org.ccoin.config.ServerConfig -import org.ccoin.database.Tables -import org.ccoin.exceptions.InvalidTransactionException -import org.ccoin.models.BlockResponse -import org.ccoin.models.MiningJobResponse -import org.ccoin.models.MiningStatsResponse -import org.ccoin.utils.CryptoUtils -import org.ccoin.utils.HashUtils -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction -import java.math.BigDecimal -import java.time.Instant - -object MiningService { - - /** Starts a mining job for a miner */ - fun startMining(minerAddress: String, difficulty: Int? = null): MiningJobResponse { - val jobId = CryptoUtils.generateJobId() - val currentDifficulty = difficulty ?: getCurrentDifficulty() - val previousHash = getLatestBlockHash() - val height = getNextBlockHeight() - val timestamp = Instant.now().epochSecond - val expiresAt = timestamp + 300 // 5 minutes - val target = "0".repeat(currentDifficulty) - - return MiningJobResponse( - jobId, target, currentDifficulty, previousHash, height, timestamp, expiresAt - ) - } - - /** Submits mining result and creates block if valid */ - fun submitMiningResult( - minerAddress: String, - nonce: Long, - hash: String, - previousHash: String, - timestamp: Long = Instant.now().epochSecond - ): BlockResponse { - val currentDifficulty = getCurrentDifficulty() - - // Validate hash meets difficulty requirements - if (!CryptoUtils.isValidHash(hash, currentDifficulty)) { - throw InvalidTransactionException("Hash does not meet difficulty requirements") - } - - // Validate previous hash matches latest block - val latestHash = getLatestBlockHash() - if (previousHash != latestHash) { - throw InvalidTransactionException("Previous hash does not match latest block") - } - - val height = getNextBlockHeight() - val reward = ServerConfig.miningReward - val merkleRoot = calculateMerkleRoot(emptyList()) // No transactions for now - - return transaction { - // Create block - Tables.Blocks.insert { - it[Tables.Blocks.hash] = hash - it[Tables.Blocks.previousHash] = if (previousHash == "0".repeat(64)) null else previousHash - it[Tables.Blocks.merkleRoot] = merkleRoot - it[Tables.Blocks.timestamp] = timestamp - it[Tables.Blocks.difficulty] = currentDifficulty - it[Tables.Blocks.nonce] = nonce - it[Tables.Blocks.minerAddress] = minerAddress - it[Tables.Blocks.reward] = BigDecimal.valueOf(reward) - it[transactionCount] = 0 - } - - // Reward miner with genesis transaction - TransactionService.createGenesisTransaction(minerAddress, reward) - - BlockResponse( - hash, if (previousHash == "0".repeat(64)) null else previousHash, merkleRoot, - timestamp, currentDifficulty, nonce, minerAddress, reward, height, 0, 0 - ) - } - } - - /** Gets current mining difficulty */ - fun getCurrentDifficulty(): Int = ServerConfig.miningDifficulty - - /** Gets mining statistics for a miner */ - fun getMinerStats(minerAddress: String): MiningStatsResponse = transaction { - val blocks = Tables.Blocks.selectAll() - .where { Tables.Blocks.minerAddress eq minerAddress } - .orderBy(Tables.Blocks.timestamp, SortOrder.DESC) - - val totalBlocks = blocks.count().toInt() - val totalReward = blocks.sumOf { it[Tables.Blocks.reward] }.toDouble() - val lastBlockMined = blocks.firstOrNull()?.get(Tables.Blocks.timestamp)?.toLong() - - MiningStatsResponse( - minerAddress, totalBlocks, totalReward, lastBlockMined, getCurrentDifficulty() - ) - } - - /** Gets latest block hash */ - private fun getLatestBlockHash(): String = transaction { - Tables.Blocks.selectAll() - .orderBy(Tables.Blocks.height, SortOrder.DESC) - .limit(1) - .map { it[Tables.Blocks.hash] } - .singleOrNull() ?: "0".repeat(64) // Genesis hash - } - - /** Gets next block height */ - private fun getNextBlockHeight(): Int = transaction { - val latestHeight = Tables.Blocks.selectAll() - .orderBy(Tables.Blocks.height, SortOrder.DESC) - .limit(1) - .map { it[Tables.Blocks.height] } - .singleOrNull() ?: 0 - - latestHeight + 1 - } - - /** Calculates merkle root for transactions */ - private fun calculateMerkleRoot(transactionHashes: List): String { - return if (transactionHashes.isEmpty()) { - HashUtils.sha256Hex("empty_block") - } else { - CryptoUtils.calculateMerkleRoot(transactionHashes) - } - } - - /** Gets network hash rate estimate */ - fun getNetworkHashRate(): Double = transaction { - val recentBlocks = Tables.Blocks.selectAll() - .orderBy(Tables.Blocks.timestamp, SortOrder.DESC) - .limit(100) - - if (recentBlocks.count() < 2) return@transaction 0.0 - - val blocks = recentBlocks.toList() - val timeSpan = blocks.first()[Tables.Blocks.timestamp] - blocks.last()[Tables.Blocks.timestamp] - val difficulty = getCurrentDifficulty() - - if (timeSpan <= 0) return@transaction 0.0 - - // Rough estimate: (blocks * 2^difficulty) / time_span - (blocks.size * Math.pow(2.0, difficulty.toDouble())) / timeSpan - } - - /** Gets average block time */ - fun getAverageBlockTime(): Long = transaction { - val recentBlocks = Tables.Blocks.selectAll() - .orderBy(Tables.Blocks.timestamp, SortOrder.DESC) - .limit(100) - .map { it[Tables.Blocks.timestamp] } - - if (recentBlocks.size < 2) return@transaction 0L - - val timeDiffs = recentBlocks.zipWithNext { a, b -> a - b } - timeDiffs.average().toLong() - } - - /** Gets total number of active miners */ - fun getActiveMinersCount(): Int = transaction { - val oneDayAgo = Instant.now().epochSecond - 86400 - - Tables.Blocks.selectAll() - .where { Tables.Blocks.timestamp greater oneDayAgo } - .groupBy(Tables.Blocks.minerAddress) - .count().toInt() - } - - /** Validates mining job */ - fun validateMiningJob(jobId: String, hash: String, nonce: Long): Boolean { - // Simple validation - in production you'd store job details - return CryptoUtils.isValidHash(hash, getCurrentDifficulty()) - } - - /** Gets pending transactions for mining */ - fun getPendingTransactionsForMining(limit: Int = 100): List = transaction { - Tables.Transactions.selectAll() - .where { Tables.Transactions.status eq "pending" } - .orderBy(Tables.Transactions.timestamp, SortOrder.ASC) - .limit(limit) - .map { it[Tables.Transactions.hash] } - } -} - diff --git a/server/src/main/kotlin/org/ccoin/services/TransactionService.kt b/server/src/main/kotlin/org/ccoin/services/TransactionService.kt deleted file mode 100644 index b7ab140..0000000 --- a/server/src/main/kotlin/org/ccoin/services/TransactionService.kt +++ /dev/null @@ -1,243 +0,0 @@ -package org.ccoin.services - -import org.ccoin.database.Tables -import org.ccoin.exceptions.InsufficientFundsException -import org.ccoin.exceptions.InvalidTransactionException -import org.ccoin.exceptions.WalletNotFoundException -import org.ccoin.models.TransactionResponse -import org.ccoin.models.TransactionStatus -import org.ccoin.utils.CryptoUtils -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction -import java.math.BigDecimal -import java.time.Instant - -object TransactionService { - - /** Sends a transaction between wallets */ - fun sendTransaction( - fromAddress: String, - toAddress: String, - amount: Double, - password: String, - fee: Double = 0.0, - memo: String? = null - ): TransactionResponse { - val hash = CryptoUtils.generateTransactionHash(fromAddress, toAddress, amount, Instant.now().epochSecond) - val timestamp = Instant.now().epochSecond - val totalAmount = BigDecimal.valueOf(amount + fee) - - return transaction { - // Check if sender wallet exists - val wallet = Tables.Wallets.selectAll() - .where { Tables.Wallets.address eq fromAddress } - .singleOrNull() ?: throw WalletNotFoundException(fromAddress) - - val storedHash = wallet[Tables.Wallets.passwordHash] - ?: throw InvalidTransactionException("Wallet has no password set") - - if (!CryptoUtils.verifyPassword(password, storedHash)) { - throw InvalidTransactionException("Invalid password") - } - - // Check if sender wallet exists and has sufficient balance - val fromBalance = wallet[Tables.Wallets.balance] - - if (fromBalance < totalAmount) { - throw InsufficientFundsException(fromAddress, amount + fee, fromBalance.toDouble()) - } - - // Check if recipient wallet exists - val recipientExists = Tables.Wallets.selectAll() - .where { Tables.Wallets.address eq toAddress } - .count() > 0 - - if (!recipientExists) { - throw WalletNotFoundException(toAddress) - } - - // Create transaction record - Tables.Transactions.insert { - it[Tables.Transactions.hash] = hash - it[Tables.Transactions.fromAddress] = fromAddress - it[Tables.Transactions.toAddress] = toAddress - it[Tables.Transactions.amount] = BigDecimal.valueOf(amount) - it[Tables.Transactions.fee] = BigDecimal.valueOf(fee) - it[Tables.Transactions.memo] = memo - it[Tables.Transactions.timestamp] = timestamp - it[status] = "confirmed" - } - - // Update sender balance - val senderCurrentBalance = Tables.Wallets.selectAll() - .where { Tables.Wallets.address eq fromAddress } - .map { it[Tables.Wallets.balance] } - .single() - - Tables.Wallets.update({ Tables.Wallets.address eq fromAddress }) { - it[balance] = senderCurrentBalance.subtract(totalAmount) - it[lastActivity] = timestamp - } - - // Update recipient balance - val recipientCurrentBalance = Tables.Wallets.selectAll() - .where { Tables.Wallets.address eq toAddress } - .map { it[Tables.Wallets.balance] } - .single() - - Tables.Wallets.update({ Tables.Wallets.address eq toAddress }) { - it[balance] = recipientCurrentBalance.add(BigDecimal.valueOf(amount)) - it[lastActivity] = timestamp - } - - TransactionResponse( - hash, fromAddress, toAddress, amount, fee, memo, null, timestamp, TransactionStatus.CONFIRMED, 0 - ) - } - } - - /** Gets transaction by hash */ - fun getTransaction(hash: String): TransactionResponse? = transaction { - Tables.Transactions.selectAll().where { Tables.Transactions.hash eq hash } - .map { - TransactionResponse( - it[Tables.Transactions.hash], - it[Tables.Transactions.fromAddress], - it[Tables.Transactions.toAddress], - it[Tables.Transactions.amount].toDouble(), - it[Tables.Transactions.fee].toDouble(), - it[Tables.Transactions.memo], - it[Tables.Transactions.blockHash], - it[Tables.Transactions.timestamp], - TransactionStatus.valueOf(it[Tables.Transactions.status].uppercase()), - it[Tables.Transactions.confirmations] - ) - }.singleOrNull() - } - - /** Gets transaction history for an address */ - fun getTransactionHistory( - address: String, - limit: Int = 50, - offset: Int = 0 - ): List = transaction { - Tables.Transactions.selectAll() - .where { - (Tables.Transactions.fromAddress eq address) or (Tables.Transactions.toAddress eq address) - } - .orderBy(Tables.Transactions.timestamp, SortOrder.DESC) - .limit(limit) - .offset(offset.toLong()) - .map { - TransactionResponse( - it[Tables.Transactions.hash], - it[Tables.Transactions.fromAddress], - it[Tables.Transactions.toAddress], - it[Tables.Transactions.amount].toDouble(), - it[Tables.Transactions.fee].toDouble(), - it[Tables.Transactions.memo], - it[Tables.Transactions.blockHash], - it[Tables.Transactions.timestamp], - TransactionStatus.valueOf(it[Tables.Transactions.status].uppercase()), - it[Tables.Transactions.confirmations] - ) - } - } - - /** Gets pending transactions */ - fun getPendingTransactions(): List = transaction { - Tables.Transactions.selectAll() - .where { Tables.Transactions.status eq "pending" } - .orderBy(Tables.Transactions.timestamp, SortOrder.ASC) - .map { - TransactionResponse( - it[Tables.Transactions.hash], - it[Tables.Transactions.fromAddress], - it[Tables.Transactions.toAddress], - it[Tables.Transactions.amount].toDouble(), - it[Tables.Transactions.fee].toDouble(), - it[Tables.Transactions.memo], - it[Tables.Transactions.blockHash], - it[Tables.Transactions.timestamp], - TransactionStatus.valueOf(it[Tables.Transactions.status].uppercase()), - it[Tables.Transactions.confirmations] - ) - } - } - - /** Updates transaction status */ - fun updateTransactionStatus(hash: String, status: TransactionStatus): Boolean = transaction { - val updated = Tables.Transactions.update({ Tables.Transactions.hash eq hash }) { - it[Tables.Transactions.status] = status.name.lowercase() - } - updated > 0 - } - - /** Adds transaction to block */ - fun addTransactionToBlock(transactionHash: String, blockHash: String): Boolean = transaction { - val updated = Tables.Transactions.update({ Tables.Transactions.hash eq transactionHash }) { - it[Tables.Transactions.blockHash] = blockHash - it[status] = "confirmed" - } - updated > 0 - } - - /** Gets total transaction count */ - fun getTotalTransactionCount(): Long = transaction { - Tables.Transactions.selectAll().count() - } - - /** Gets transaction count for address */ - fun getTransactionCountForAddress(address: String): Long = transaction { - Tables.Transactions.selectAll() - .where { - (Tables.Transactions.fromAddress eq address) or (Tables.Transactions.toAddress eq address) - } - .count() - } - - /** Creates a genesis transaction (mining reward) */ - fun createGenesisTransaction(toAddress: String, amount: Double): TransactionResponse { - val hash = CryptoUtils.generateTransactionHash(null, toAddress, amount, Instant.now().epochSecond) - val timestamp = Instant.now().epochSecond - - return transaction { - // Check if recipient wallet exists - val recipientExists = Tables.Wallets.selectAll() - .where { Tables.Wallets.address eq toAddress } - .count() > 0 - - if (!recipientExists) { - throw WalletNotFoundException(toAddress) - } - - // Create genesis transaction - Tables.Transactions.insert { - it[Tables.Transactions.hash] = hash - it[Tables.Transactions.fromAddress] = null - it[Tables.Transactions.toAddress] = toAddress - it[Tables.Transactions.amount] = BigDecimal.valueOf(amount) - it[Tables.Transactions.fee] = BigDecimal.ZERO - it[Tables.Transactions.memo] = "Mining reward" - it[Tables.Transactions.timestamp] = timestamp - it[status] = "confirmed" - } - - // Update recipient balance - val recipientCurrentBalance = Tables.Wallets.selectAll() - .where { Tables.Wallets.address eq toAddress } - .map { it[Tables.Wallets.balance] } - .single() - - Tables.Wallets.update({ Tables.Wallets.address eq toAddress }) { - it[balance] = recipientCurrentBalance.add(BigDecimal.valueOf(amount)) - it[lastActivity] = timestamp - } - - TransactionResponse( - hash, null, toAddress, amount, 0.0, "Mining reward", null, timestamp, TransactionStatus.CONFIRMED, 0 - ) - } - } -} - diff --git a/server/src/main/kotlin/org/ccoin/services/ValidationService.kt b/server/src/main/kotlin/org/ccoin/services/ValidationService.kt deleted file mode 100644 index f4766e8..0000000 --- a/server/src/main/kotlin/org/ccoin/services/ValidationService.kt +++ /dev/null @@ -1,201 +0,0 @@ -package org.ccoin.services - -import org.ccoin.config.ServerConfig -import org.ccoin.exceptions.InvalidTransactionException -import org.ccoin.utils.CryptoUtils -import org.ccoin.utils.HashUtils - -object ValidationService { - /** Validates wallet address format */ - fun validateWalletAddress(address: String): Boolean = CryptoUtils.isValidAddress(address) - - /** Validates transaction amount */ - fun validateTransactionAmount(amount: Double): Boolean = amount > 0 && amount <= Double.MAX_VALUE && !amount.isNaN() && !amount.isInfinite() - - /** Validates transaction fee */ - fun validateTransactionFee(fee: Double): Boolean = fee >= 0 && fee <= Double.MAX_VALUE && !fee.isNaN() && !fee.isInfinite() - - /** Validates memo length */ - fun validateMemo(memo: String?): Boolean = memo == null || memo.length <= ServerConfig.maxMemoLength - - /** Validates transaction hash format */ - fun validateTransactionHash(hash: String): Boolean = HashUtils.isValidSha256Hash(hash) - - /** Validates block hash format */ - fun validateBlockHash(hash: String): Boolean = HashUtils.isValidSha256Hash(hash) - - /** Validates mining difficulty */ - fun validateMiningDifficulty(difficulty: Int): Boolean = difficulty in 1..32 - - /** Validates mining nonce */ - fun validateMiningNonce(nonce: Long): Boolean = nonce >= 0 - - /** Validates timestamp */ - fun validateTimestamp(timestamp: Long): Boolean { - val now = System.currentTimeMillis() / 1000 - val oneHourAgo = now - 3600 - val oneHourFromNow = now + 3600 - return timestamp in oneHourAgo..oneHourFromNow - } - - /** Validates wallet label */ - fun validateWalletLabel(label: String?): Boolean = label == null || (label.isNotBlank() && label.length <= 255) - - /** Validates page number for pagination */ - fun validatePageNumber(page: Int): Boolean = page >= 1 - - /** Validates page size for pagination */ - fun validatePageSize(pageSize: Int): Boolean = pageSize in 1..ServerConfig.maxPageSize - - /** Validates mining hash meets difficulty requirement */ - fun validateMiningHash(hash: String, difficulty: Int): Boolean = validateBlockHash(hash) && CryptoUtils.isValidHash(hash, difficulty) - - /** Validates complete transaction data */ - fun validateTransaction( - fromAddress: String?, - toAddress: String, - amount: Double, - password: String, - fee: Double, - memo: String? - ): ValidationResult { - val errors = mutableListOf() - - // Validate addresses - if (fromAddress != null && !validateWalletAddress(fromAddress)) { - errors.add("Invalid from address format") - } - - if (!validateWalletAddress(toAddress)) { - errors.add("Invalid to address format") - } - - if (fromAddress == toAddress) { - errors.add("Cannot send to same address") - } - - // Validate amounts - if (!validateTransactionAmount(amount)) { - errors.add("Invalid transaction amount") - } - - if (!validateTransactionFee(fee)) { - errors.add("Invalid transaction fee") - } - - // Validate memo - if (!validateMemo(memo)) { - errors.add("Memo too long (max ${ServerConfig.maxMemoLength} characters)") - } - - return ValidationResult(errors.isEmpty(), errors) - } - - /** Validates complete block data */ - fun validateBlock( - hash: String, - previousHash: String?, - merkleRoot: String, - timestamp: Long, - difficulty: Int, - nonce: Long, - minerAddress: String - ): ValidationResult { - val errors = mutableListOf() - - // Validate hash - if (!validateBlockHash(hash)) { - errors.add("Invalid block hash format") - } - - // Validate previous hash - if (previousHash != null && !validateBlockHash(previousHash)) { - errors.add("Invalid previous hash format") - } - - // Validate merkle root - if (!validateBlockHash(merkleRoot)) { - errors.add("Invalid merkle root format") - } - - // Validate timestamp - if (!validateTimestamp(timestamp)) { - errors.add("Invalid timestamp (must be within 1 hour of current time)") - } - - // Validate difficulty - if (!validateMiningDifficulty(difficulty)) { - errors.add("Invalid mining difficulty (must be between 1 and 32)") - } - - // Validate nonce - if (!validateMiningNonce(nonce)) { - errors.add("Invalid nonce (must be non-negative)") - } - - // Validate miner address - if (!validateWalletAddress(minerAddress)) { - errors.add("Invalid miner address format") - } - - // Validate hash meets difficulty - if (!validateMiningHash(hash, difficulty)) { - errors.add("Hash does not meet difficulty requirements") - } - - return ValidationResult(errors.isEmpty(), errors) - } - - /** Validates wallet creation data */ - fun validateWalletCreation(label: String?): ValidationResult { - val errors = mutableListOf() - - if (!validateWalletLabel(label)) { - errors.add("Invalid wallet label") - } - - return ValidationResult(errors.isEmpty(), errors) - } - - /** Validates pagination parameters */ - fun validatePagination(page: Int, pageSize: Int): ValidationResult { - val errors = mutableListOf() - - if (!validatePageNumber(page)) { - errors.add("Page number must be >= 1") - } - - if (!validatePageSize(pageSize)) { - errors.add("Page size must be between 1 and ${ServerConfig.maxPageSize}") - } - - return ValidationResult(errors.isEmpty(), errors) - } - - /** Sanitizes user input */ - fun sanitizeInput(input: String): String { - return input.trim() - .replace(Regex("[\\r\\n\\t]"), " ") - .replace(Regex("\\s+"), " ") - } - - /** Validates hex string */ - fun validateHexString(hex: String, expectedLength: Int? = null): Boolean { - if (!HashUtils.isValidHex(hex)) return false - return expectedLength == null || hex.length == expectedLength - } -} - -/** Result of validation with success status and error messages */ -data class ValidationResult( - val isValid: Boolean, - val errors: List = emptyList() -) { - fun getErrorMessage(): String = errors.joinToString(", ") - - fun throwIfInvalid() { - if (!isValid) { - throw InvalidTransactionException(getErrorMessage()) - } - } -} diff --git a/server/src/main/kotlin/org/ccoin/services/WalletService.kt b/server/src/main/kotlin/org/ccoin/services/WalletService.kt deleted file mode 100644 index 6954403..0000000 --- a/server/src/main/kotlin/org/ccoin/services/WalletService.kt +++ /dev/null @@ -1,150 +0,0 @@ -package org.ccoin.services - -import org.ccoin.database.Tables -import org.ccoin.exceptions.WalletNotFoundException -import org.ccoin.models.WalletResponse -import org.ccoin.utils.CryptoUtils -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction -import java.math.BigDecimal -import java.time.Instant - -object WalletService { - - /** Creates a new wallet with optional label */ - fun createWallet(label: String? = null, password: String): WalletResponse { - val address = CryptoUtils.generateWalletAddress() - val timestamp = Instant.now().epochSecond - val passwordHash = CryptoUtils.hashPassword(password) - - return transaction { - Tables.Wallets.insert { - it[Tables.Wallets.address] = address - it[Tables.Wallets.label] = label - it[Tables.Wallets.passwordHash] = passwordHash - it[createdAt] = timestamp - } - - WalletResponse( - address = address, - balance = 0.0, - label = label, - passwordHash = passwordHash, - createdAt = timestamp, - lastActivity = null - ) - } - } - - /** Gets wallet by address */ - fun getWallet(address: String): WalletResponse? = transaction { - Tables.Wallets.selectAll().where { Tables.Wallets.address eq address } - .map { - WalletResponse( - it[Tables.Wallets.address], - it[Tables.Wallets.balance].toDouble(), - it[Tables.Wallets.label], - it[Tables.Wallets.passwordHash].toString(), - it[Tables.Wallets.createdAt], - it[Tables.Wallets.lastActivity] - ) - }.singleOrNull() - } - - /** Gets wallet balance */ - fun getWalletBalance(address: String): Double = transaction { - Tables.Wallets.selectAll().where { Tables.Wallets.address eq address } - .map { it[Tables.Wallets.balance].toDouble() } - .singleOrNull() ?: throw WalletNotFoundException(address) - } - - /** Updates wallet balance */ - fun updateBalance(address: String, amount: BigDecimal): Boolean = transaction { - val currentBalance = Tables.Wallets.selectAll() - .where { Tables.Wallets.address eq address } - .map { it[Tables.Wallets.balance] } - .singleOrNull() ?: return@transaction false - - val updated = Tables.Wallets.update({ Tables.Wallets.address eq address }) { - it[balance] = currentBalance.add(amount) - it[lastActivity] = Instant.now().epochSecond - } - updated > 0 - } - - /** Sets wallet balance to specific amount */ - fun setBalance(address: String, amount: BigDecimal): Boolean = transaction { - val updated = Tables.Wallets.update({ Tables.Wallets.address eq address }) { - it[balance] = amount - it[lastActivity] = Instant.now().epochSecond - } - updated > 0 - } - - /** Updates wallet label */ - fun updateLabel(address: String, label: String?): Boolean = transaction { - val updated = Tables.Wallets.update({ Tables.Wallets.address eq address }) { - it[Tables.Wallets.label] = label - } - updated > 0 - } - - /** Checks if wallet exists */ - fun walletExists(address: String): Boolean = transaction { - Tables.Wallets.selectAll().where { Tables.Wallets.address eq address }.count() > 0 - } - - /** Gets all wallets with pagination */ - fun getAllWallets(limit: Int = 50, offset: Int = 0): List = transaction { - Tables.Wallets.selectAll() - .orderBy(Tables.Wallets.createdAt, SortOrder.DESC) - .limit(limit) - .offset(offset.toLong()) - .map { - WalletResponse( - it[Tables.Wallets.address], - it[Tables.Wallets.balance].toDouble(), - it[Tables.Wallets.label], - it[Tables.Wallets.passwordHash].toString(), - it[Tables.Wallets.createdAt], - it[Tables.Wallets.lastActivity] - ) - } - } - - /** Gets total number of wallets */ - fun getTotalWalletCount(): Long = transaction { - Tables.Wallets.selectAll().count() - } - - /** Gets wallets with balance greater than specified amount */ - fun getWalletsWithBalance(minBalance: Double): List = transaction { - Tables.Wallets.selectAll().where { Tables.Wallets.balance greater BigDecimal.valueOf(minBalance) } - .orderBy(Tables.Wallets.balance, SortOrder.DESC) - .map { - WalletResponse( - it[Tables.Wallets.address], - it[Tables.Wallets.balance].toDouble(), - it[Tables.Wallets.label], - it[Tables.Wallets.passwordHash].toString(), - it[Tables.Wallets.createdAt], - it[Tables.Wallets.lastActivity] - ) - } - } - - /** Updates last activity timestamp */ - fun updateLastActivity(address: String): Boolean = transaction { - val updated = Tables.Wallets.update({ Tables.Wallets.address eq address }) { - it[lastActivity] = Instant.now().epochSecond - } - updated > 0 - } - - /** Gets total supply across all wallets */ - fun getTotalSupply(): Double = transaction { - Tables.Wallets.select(Tables.Wallets.balance.sum()) - .single()[Tables.Wallets.balance.sum()]?.toDouble() ?: 0.0 - } -} - diff --git a/server/src/main/kotlin/org/ccoin/utils/CryptoUtils.kt b/server/src/main/kotlin/org/ccoin/utils/CryptoUtils.kt deleted file mode 100644 index 271724f..0000000 --- a/server/src/main/kotlin/org/ccoin/utils/CryptoUtils.kt +++ /dev/null @@ -1,129 +0,0 @@ -package org.ccoin.utils - -import java.security.MessageDigest -import java.security.SecureRandom -import kotlin.random.Random - -object CryptoUtils { - private val secureRandom = SecureRandom() - - // Word list for address generation - private val words = listOf( - "phoenix", "dragon", "tiger", "eagle", "wolf", "lion", "bear", "shark", - "falcon", "raven", "cobra", "viper", "panther", "jaguar", "leopard", "cheetah", - "thunder", "lightning", "storm", "blizzard", "tornado", "hurricane", "cyclone", "tempest", - "crystal", "diamond", "emerald", "ruby", "sapphire", "topaz", "amethyst", "opal", - "shadow", "ghost", "phantom", "spirit", "wraith", "specter", "demon", "angel", - "fire", "ice", "earth", "wind", "water", "metal", "wood", "void", - "star", "moon", "sun", "comet", "meteor", "galaxy", "nebula", "cosmos", - "blade", "sword", "arrow", "spear", "shield", "armor", "crown", "throne", - "mountain", "ocean", "forest", "desert", "valley", "river", "lake", "cave", - "knight", "warrior", "mage", "archer", "rogue", "paladin", "wizard", "sage" - ) - - /** - * Generates a wallet address in format: random_word:random_6_digits - * Example: "phoenix:123456", "dragon:654321" - */ - fun generateWalletAddress(): String { - val randomWord = words[secureRandom.nextInt(words.size)] - val randomDigits = String.format("%06d", secureRandom.nextInt(1000000)) - return "$randomWord:$randomDigits" - } - - /** Validates if an address follows the correct format */ - fun isValidAddress(address: String): Boolean { - val parts = address.split(":") - if (parts.size != 2) return false - - val word = parts[0] - val digits = parts[1] - - return word.isNotEmpty() && - word.all { it.isLetter() } && - digits.length == 6 && - digits.all { it.isDigit() } - } - - /** Generates SHA-256 hash of input string */ - fun sha256(input: String): String { - return MessageDigest.getInstance("SHA-256") - .digest(input.toByteArray()) - .joinToString("") { "%02x".format(it) } - } - - /** Hashes password */ - fun hashPassword(password: String): String = sha256("ccoin_password_$password") - - /** Generates a transaction hash */ - fun generateTransactionHash( - fromAddress: String?, - toAddress: String, - amount: Double, - timestamp: Long, - nonce: Long = secureRandom.nextLong() - ): String { - val input = "${fromAddress ?: "genesis"}:$toAddress:$amount:$timestamp:$nonce" - return sha256(input) - } - - /** Generates a block hash */ - fun generateBlockHash( - previousHash: String, - merkleRoot: String, - timestamp: Long, - difficulty: Int, - nonce: Long - ): String { - val input = "$previousHash:$merkleRoot:$timestamp:$difficulty:$nonce" - return sha256(input) - } - - /** Validates if a hash meets the mining difficulty requirement */ - fun isValidHash(hash: String, difficulty: Int): Boolean { - val target = "0".repeat(difficulty) - return hash.startsWith(target) - } - - /** Generates a mining job id */ - fun generateJobId(): String = sha256("job:${System.currentTimeMillis()}:${secureRandom.nextLong()}").take(16) - - /** Calculates a merkle root from transaction hashes */ - fun calculateMerkleRoot(transactionHashes: List): String { - if (transactionHashes.isEmpty()) { - return sha256("empty") - } - - if (transactionHashes.size == 1) { - return transactionHashes[0] - } - - var hashes = transactionHashes.toMutableList() - - while (hashes.size > 1) { - val newHashes = mutableListOf() - - for (i in hashes.indices step 2) { - val left = hashes[i] - val right = if (i + 1 < hashes.size) hashes[i + 1] else left - newHashes.add(sha256("$left:$right")) - } - - hashes = newHashes - } - - return hashes[0] - } - - /** Generates a random nonce for mining */ - fun generateNonce(): Long = secureRandom.nextLong() - - /** Validates transaction hash format */ - fun isValidTransactionHash(hash: String): Boolean = hash.length == 64 && hash.all { it.isDigit() || it.lowercaseChar() in 'a'..'f' } - - /** Validates block has format */ - fun isValidBlockHash(hash: String): Boolean = hash.length == 64 && hash.all { it.isDigit() || it.lowercaseChar() in 'a'..'f' } - - /** Verifies password hash */ - fun verifyPassword(password: String, storedHash: String): Boolean = hashPassword(password) == storedHash -} diff --git a/server/src/main/kotlin/org/ccoin/utils/Extensions.kt b/server/src/main/kotlin/org/ccoin/utils/Extensions.kt deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/main/kotlin/org/ccoin/utils/HashUtils.kt b/server/src/main/kotlin/org/ccoin/utils/HashUtils.kt deleted file mode 100644 index 9f6964d..0000000 --- a/server/src/main/kotlin/org/ccoin/utils/HashUtils.kt +++ /dev/null @@ -1,80 +0,0 @@ -package org.ccoin.utils - -import java.security.MessageDigest -import java.security.SecureRandom - -object HashUtils { - private val secureRandom = SecureRandom() - - /** Computes SHA-256 hash of a byte array */ - fun sha256(data: ByteArray): ByteArray = MessageDigest.getInstance("SHA-256").digest(data) - - /** Computes SHA-256 hash of a string and returns hex string */ - fun sha256Hex(input: String): String = sha256(input.toByteArray()).toHexString() - - /** Computes double SHA-256 hash (like Bitcoin) */ - fun doubleSha256(data: ByteArray): ByteArray = sha256(sha256(data)) - - /** Computes double SHA-256 hash and returns hex string */ - fun doubleSha256Hex(input: String): String = doubleSha256(input.toByteArray()).toHexString() - - /** Converts byte array to hex string */ - fun ByteArray.toHexString(): String = joinToString("") { "%02x".format(it) } - - /** Converts hex string to byte array */ - fun String.hexToByteArray(): ByteArray { - require(length % 2 == 0) { "Hex string must have even length" } - return chunked(2).map { it.toInt(16).toByte() }.toByteArray() - } - - /** Validates if string is valid hex */ - fun isValidHex(hex: String): Boolean = hex.all { it.isDigit() || it.lowercaseChar() in 'a'..'f' } - - /** Generates a random hash for testing purposes */ - fun generateRandomHash(): String { - val randomBytes = ByteArray(32) - secureRandom.nextBytes(randomBytes) - return randomBytes.toHexString() - } - - /** Computes hash with salt for additional security */ - fun hashWithSalt(input: String, salt: String): String = sha256Hex("$input:$salt") - - /** Generates a random salt */ - fun generateSalt(length: Int = 16): String { - val saltBytes = ByteArray(length) - secureRandom.nextBytes(saltBytes) - return saltBytes.toHexString() - } - - /** Computes HMAC-SHA256 */ - fun hmacSha256(key: String, message: String): String { - val keyBytes = key.toByteArray() - val messageBytes = message.toByteArray() - - val blockSize = 64 - val adjustedKey = when { - keyBytes.size > blockSize -> sha256(keyBytes) - keyBytes.size < blockSize -> keyBytes + ByteArray(blockSize - keyBytes.size) - else -> keyBytes - } - - val outerPad = ByteArray(blockSize) { (adjustedKey[it].toInt() xor 0x5c).toByte() } - val innerPad = ByteArray(blockSize) { (adjustedKey[it].toInt() xor 0x36).toByte() } - - val innerHash = sha256(innerPad + messageBytes) - val finalHash = sha256(outerPad + innerHash) - - return finalHash.toHexString() - } - - /** Validates hash format (64 character hex string for SHA-256) */ - fun isValidSha256Hash(hash: String): Boolean = hash.length == 64 && isValidHex(hash) - - /** Computes checksum for data integrity */ - fun computeChecksum(data: String): String = sha256Hex(data).take(8) - - /** Validates data against checksum */ - fun validateChecksum(data: String, checksum: String): Boolean = computeChecksum(data) == checksum -} - diff --git a/server/src/main/resources/application.conf b/server/src/main/resources/application.conf deleted file mode 100644 index 3ad67f8..0000000 --- a/server/src/main/resources/application.conf +++ /dev/null @@ -1,9 +0,0 @@ -ktor { - deployment { - port = 8080 - port = ${?PORT} - } - application { - modules = [ org.ccoin.ServerKt.module] - } -} diff --git a/server/src/main/resources/db/migration/V1__Create_wallets_table.sql b/server/src/main/resources/db/migration/V1__Create_wallets_table.sql deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/main/resources/db/migration/V2__Create_transactions_table.sql b/server/src/main/resources/db/migration/V2__Create_transactions_table.sql deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/main/resources/db/migration/V3__Create_blocks_table.sql b/server/src/main/resources/db/migration/V3__Create_blocks_table.sql deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/main/resources/db/migration/V4__Add_indexes.sql b/server/src/main/resources/db/migration/V4__Add_indexes.sql deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml deleted file mode 100644 index 89d89e0..0000000 --- a/server/src/main/resources/logback.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - %d{MM-dd HH:mm:ss} %-5level - %msg%n - - - - - - - - - - diff --git a/server/src/test/kotlin/org/ccoin/ServerTest.kt b/server/src/test/kotlin/org/ccoin/ServerTest.kt deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/test/kotlin/org/ccoin/services/MiningServiceTest.kt b/server/src/test/kotlin/org/ccoin/services/MiningServiceTest.kt deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/test/kotlin/org/ccoin/services/TransactionServiceTest.kt b/server/src/test/kotlin/org/ccoin/services/TransactionServiceTest.kt deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/test/kotlin/org/ccoin/services/WalletServiceTest.kt b/server/src/test/kotlin/org/ccoin/services/WalletServiceTest.kt deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/test/kotlin/org/ccoin/utils/CryptoUtilsTest.kt b/server/src/test/kotlin/org/ccoin/utils/CryptoUtilsTest.kt deleted file mode 100644 index e69de29..0000000