feat: added mining service

This commit is contained in:
darwincereska
2025-12-17 10:32:53 -05:00
parent a6021f9523
commit 6d360df21d

View File

@@ -0,0 +1,185 @@
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>): 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<String> = transaction {
Tables.Transactions.selectAll()
.where { Tables.Transactions.status eq "pending" }
.orderBy(Tables.Transactions.timestamp, SortOrder.ASC)
.limit(limit)
.map { it[Tables.Transactions.hash] }
}
}