feat: added block routes
This commit is contained in:
@@ -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")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user