diff --git a/server/src/main/kotlin/org/ccoin/routes/BlockRoutes.kt b/server/src/main/kotlin/org/ccoin/routes/BlockRoutes.kt index e69de29..98e1450 100644 --- a/server/src/main/kotlin/org/ccoin/routes/BlockRoutes.kt +++ b/server/src/main/kotlin/org/ccoin/routes/BlockRoutes.kt @@ -0,0 +1,280 @@ +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 + +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(mapOf( + "hash" to hash, + "exists" to 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 0..100) { + call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Limit must be between 1 and 100")) + return@get + } + + val blocks = BlockService.getLatestBlocks(limit) + call.respond(mapOf( + "blocks" to blocks, + "count" to 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) + ) + )) + + } 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(mapOf( + "blocks" to blocks, + "fromTime" to fromTime, + "toTime" to toTime, + "count" to 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(mapOf( + "blocks" to blocks, + "difficulty" to difficulty, + "count" to 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(mapOf( + "totalBlocks" to totalBlocks, + "latestHeight" to latestHeight, + "latestHash" to latestHash, + "averageBlockTime" to averageBlockTime, + "totalRewardsDistributed" to totalRewards + )) + + } catch (e: Exception) { + call.respond(HttpStatusCode.InternalServerError, mapOf("error" to (e.message ?: "Failed to get blockchain statistics"))) + } + } + } +}