Files
blackroad-operating-system/backend/app/services/blockchain.py
2025-11-16 01:47:22 -06:00

176 lines
5.4 KiB
Python

"""Blockchain service"""
import hashlib
import json
from datetime import datetime
from typing import List, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, desc
from app.models.blockchain import Block, Transaction, Wallet
from app.models.user import User
from app.config import settings
from app.services.crypto import wallet_crypto, WalletKeyDecryptionError
import secrets
class BlockchainService:
"""Blockchain service for RoadCoin"""
@staticmethod
def calculate_hash(index: int, timestamp: str, previous_hash: str,
transactions: List[dict], nonce: int) -> str:
"""Calculate block hash"""
data = f"{index}{timestamp}{previous_hash}{json.dumps(transactions)}{nonce}"
return hashlib.sha256(data.encode()).hexdigest()
@staticmethod
async def create_genesis_block(db: AsyncSession) -> Block:
"""Create the genesis block"""
result = await db.execute(select(Block).where(Block.index == 0))
existing = result.scalar_one_or_none()
if existing:
return existing
timestamp = datetime.utcnow()
genesis_hash = BlockchainService.calculate_hash(0, str(timestamp), "0", [], 0)
genesis_block = Block(
index=0,
timestamp=timestamp,
nonce=0,
previous_hash="0",
hash=genesis_hash,
difficulty=settings.BLOCKCHAIN_DIFFICULTY,
reward=0,
transaction_count=0,
is_valid=True
)
db.add(genesis_block)
await db.commit()
await db.refresh(genesis_block)
return genesis_block
@staticmethod
async def get_latest_block(db: AsyncSession) -> Optional[Block]:
"""Get the latest block in the chain"""
result = await db.execute(
select(Block).order_by(desc(Block.index)).limit(1)
)
return result.scalar_one_or_none()
@staticmethod
async def mine_block(db: AsyncSession, user: User, transactions: List[Transaction]) -> Block:
"""Mine a new block"""
latest_block = await BlockchainService.get_latest_block(db)
if not latest_block:
latest_block = await BlockchainService.create_genesis_block(db)
new_index = latest_block.index + 1
timestamp = datetime.utcnow()
previous_hash = latest_block.hash
difficulty = settings.BLOCKCHAIN_DIFFICULTY
# Convert transactions to dict for hashing
tx_data = [
{
"from": tx.from_address,
"to": tx.to_address,
"amount": tx.amount
}
for tx in transactions
]
# Mining (proof of work)
nonce = 0
block_hash = ""
target = "0" * difficulty
while not block_hash.startswith(target):
nonce += 1
block_hash = BlockchainService.calculate_hash(
new_index, str(timestamp), previous_hash, tx_data, nonce
)
# Create new block
new_block = Block(
index=new_index,
timestamp=timestamp,
nonce=nonce,
previous_hash=previous_hash,
hash=block_hash,
miner_id=user.id,
miner_address=user.wallet_address,
difficulty=difficulty,
reward=settings.MINING_REWARD,
transaction_count=len(transactions),
is_valid=True
)
db.add(new_block)
# Update transaction confirmations
for tx in transactions:
tx.block_id = new_block.id
tx.block_index = new_block.index
tx.is_confirmed = True
tx.confirmations = 1
tx.confirmed_at = datetime.utcnow()
# Reward miner
user.balance += settings.MINING_REWARD
await db.commit()
await db.refresh(new_block)
return new_block
@staticmethod
def generate_wallet_address() -> tuple[str, str]:
"""Generate a new wallet address and private key"""
private_key = secrets.token_hex(32)
public_key = hashlib.sha256(private_key.encode()).hexdigest()
address = "RD" + hashlib.sha256(public_key.encode()).hexdigest()[:38]
return address, private_key
@staticmethod
async def create_transaction(
db: AsyncSession,
from_address: str,
to_address: str,
amount: float,
encrypted_private_key: str
) -> Transaction:
"""Create a new transaction"""
try:
private_key = wallet_crypto.decrypt(encrypted_private_key)
except WalletKeyDecryptionError as exc:
raise WalletKeyDecryptionError(
"Unable to decrypt wallet key for transaction"
) from exc
# Generate transaction hash
tx_data = f"{from_address}{to_address}{amount}{datetime.utcnow()}"
transaction_hash = hashlib.sha256(tx_data.encode()).hexdigest()
# Sign transaction (simplified)
signature = hashlib.sha256(f"{transaction_hash}{private_key}".encode()).hexdigest()
transaction = Transaction(
transaction_hash=transaction_hash,
from_address=from_address,
to_address=to_address,
amount=amount,
signature=signature,
is_confirmed=False,
confirmations=0
)
db.add(transaction)
await db.commit()
await db.refresh(transaction)
return transaction