diff --git a/SwiftChain/Models/AccountManager.swift b/SwiftChain/Models/AccountManager.swift new file mode 100644 index 0000000..6ccab74 --- /dev/null +++ b/SwiftChain/Models/AccountManager.swift @@ -0,0 +1,72 @@ +// +// AccountManager.swift +// SwiftChain +// +// Created by Victor on 27/11/2024. +// + +class AccountManager { + private var accounts: [String: Account] = [:] + + // Mining reward amount + private let miningReward = 50 + + func getAccount(_ address: String) -> Account { + if let account = accounts[address] { + return account + } + let newAccount = Account(id: address, balance: 0) + accounts[address] = newAccount + return newAccount + } + + func canProcessTransaction(_ transaction: Transaction) -> Bool { + // Mining rewards are always valid + if transaction.type == "MINING_REWARD" { + return true + } + + let senderAccount = getAccount(transaction.sender) + return senderAccount.balance >= transaction.amount + } + + func processTransaction(_ transaction: Transaction) -> Bool { + if !canProcessTransaction(transaction) { + return false + } + + if transaction.type == "MINING_REWARD" { + let minerAccount = getAccount(transaction.receiver) + minerAccount.balance += miningReward + return true + } + + let senderAccount = getAccount(transaction.sender) + let receiverAccount = getAccount(transaction.receiver) + + senderAccount.balance -= transaction.amount + receiverAccount.balance += transaction.amount + + return true + } + + func getBalance(_ address: String) -> Int { + return getAccount(address).balance + } + + func processBlock(_ block: Block) -> Bool { + // Create temporary state to validate all transactions + let tempAccounts = accounts + + // Try to process all transactions + for transaction in block.transactions { + if !processTransaction(transaction) { + // Restore previous state if any transaction fails + accounts = tempAccounts + return false + } + } + + return true + } +} diff --git a/SwiftChain/Models/Block.swift b/SwiftChain/Models/Block.swift index 5673a5c..fd4c083 100644 --- a/SwiftChain/Models/Block.swift +++ b/SwiftChain/Models/Block.swift @@ -11,49 +11,71 @@ import CryptoSwift class Block { var hash: String - var data: String + var transactions: [Transaction] var previousHash: String var index: Int var nonce: Int var timestamp: Int - - /** - Initialize a block with the provided parts and specifications. - - - parameters: - - hash: The hash of the block - - data: The data of the block - - previousHash: The hash of the previous block - - index: The index of the block - - nonce: The nonce of the block - - timestamp: The timestamp of the block - - - returns: A beautiful new block for the blockchain. - */ - init(hash: String = "", data: String = "", previousHash: String = "", index: Int = 0, nonce: Int = 0, timestamp: Int = 0) { - self.data = data + var difficulty: Int + var miner: String? // Adresse qui recevra la récompense + + init(hash: String = "", transactions: [Transaction] = [], previousHash: String = "", + index: Int = 0, nonce: Int = 0, timestamp: Int = 0, difficulty: Int = 4) { + self.hash = hash + self.transactions = transactions self.previousHash = previousHash self.index = index self.nonce = nonce self.timestamp = timestamp - self.hash = hash + self.difficulty = difficulty } - + + func mineBlock() -> Double { + let startTime = Date() + let target = String(repeating: "0", count: difficulty) + + print("Mining block \(index) with difficulty \(difficulty)...") + + repeat { + nonce += 1 + hash = generateHash() + } while !hash.hasPrefix(target) + + let timeElapsed = Date().timeIntervalSince(startTime) + print("Block mined! Nonce: \(nonce), Hash: \(hash)") + + return timeElapsed + } + + func isValid() -> Bool { + let target = String(repeating: "0", count: difficulty) + return hash == generateHash() && hash.hasPrefix(target) + } + /** - Generate the hash of the block. - - - returns: The hash of the block + Génère le hash en incluant toutes les transactions */ func generateHash() -> String { - return data.sha256() + let txData = transactions.map { + "\($0.sender)\($0.receiver)\($0.amount)\($0.type)" + }.joined() + + return "\(previousHash)\(txData)\(String(timestamp))\(String(index))\(String(nonce))".sha256() } - + /** - Generate the timestamp of the block - - - returns: The timestamp of the block + Calcule le montant total des transactions dans le bloc */ - func generateTimestamp() -> Int { - return Int(Date().timeIntervalSince1970) + func getTotalAmount() -> Int { + return transactions.reduce(0) { $0 + $1.amount } + } + + /** + Vérifie si le bloc contient une transaction spécifique + */ + func containsTransaction(_ txId: String) -> Bool { + return transactions.contains { tx in + "\(tx.sender)\(tx.receiver)\(tx.amount)".sha256() == txId + } } } diff --git a/SwiftChain/Models/Blockchain.swift b/SwiftChain/Models/Blockchain.swift index f82add9..6ee43f3 100644 --- a/SwiftChain/Models/Blockchain.swift +++ b/SwiftChain/Models/Blockchain.swift @@ -10,79 +10,110 @@ import Foundation class Blockchain { var chain = [Block]() - + let memPool: MemPool + let accountManager: AccountManager + let minDifficulty = 2 + let maxDifficulty = 6 + let targetBlockTime = 10.0 // en secondes + + init() { + self.accountManager = AccountManager() + self.memPool = MemPool(accountManager: accountManager) + } + /** Initialize the first block of the blockchain. - Parameters: - data: The datas of the block */ - func createGenesisBlock(data: String) { + func createGenesisBlock() { let genesisBlock = Block() - genesisBlock.data = data genesisBlock.previousHash = "0000" genesisBlock.index = 0 - genesisBlock.nonce = 0 - genesisBlock.timestamp = genesisBlock.generateTimestamp() - genesisBlock.hash = genesisBlock.generateHash() + genesisBlock.timestamp = Int(Date().timeIntervalSince1970) + genesisBlock.mineBlock() chain.append(genesisBlock) print("Genesis block created -- hash: \(genesisBlock.hash)") } - + /** Initialize a new block of the blockchain. - Parameters: - data: The datas of the block */ - func createBlock(data: String) { - let newBlock = Block() - newBlock.data = data - newBlock.previousHash = chain.last!.hash - newBlock.index = chain.count - newBlock.nonce = 0 - newBlock.timestamp = newBlock.generateTimestamp() - newBlock.hash = newBlock.generateHash() - chain.append(newBlock) - - print("-- Block \(newBlock.index) created --\n hash: \(newBlock.hash)\n previous hash: \(newBlock.previousHash)\n data: \(newBlock.data)") + func createBlock(minerAddress: String) -> Block? { + guard let lastBlock = chain.last else { return nil } + + // Récupérer les transactions en attente + var transactions = memPool.getTransactionsForBlock() + + // Ajouter la récompense de minage + let miningReward = Transaction(sender: "SYSTEM", receiver: minerAddress, amount: 50, type: "MINING_REWARD") + transactions.append(miningReward) + + let newBlock = Block( + transactions: transactions, + previousHash: lastBlock.hash, + index: chain.count, + difficulty: calculateNewDifficulty() + ) + + newBlock.miner = minerAddress + newBlock.timestamp = Int(Date().timeIntervalSince1970) + let miningTime = newBlock.mineBlock() + + // Valider et traiter les transactions + if accountManager.processBlock(newBlock) { + chain.append(newBlock) + print(""" + Block \(newBlock.index) created: + Hash: \(newBlock.hash) + Previous hash: \(newBlock.previousHash) + Transactions: \(newBlock.transactions.count) + Mining time: \(String(format: "%.2f", miningTime)) seconds + """) + return newBlock + } + + return nil } - - /** - Insert a corrupted block in the blockhain. - (for testing purpose) - */ - func insertCorruptedBlock() { - let newCorruptedBlock = Block() - newCorruptedBlock.data = "Corrupted block" - newCorruptedBlock.previousHash = "1234567890" - newCorruptedBlock.index = chain.count - newCorruptedBlock.nonce = 0 - newCorruptedBlock.timestamp = newCorruptedBlock.generateTimestamp() - newCorruptedBlock.hash = newCorruptedBlock.generateHash() - chain.append(newCorruptedBlock) - - print("-- Corrupted block \(newCorruptedBlock.index) created --\n hash: \(newCorruptedBlock.hash)\n previous hash: \(newCorruptedBlock.previousHash)\n data: \(newCorruptedBlock.data)") + + private func calculateNewDifficulty() -> Int { + guard chain.count >= 2 else { return minDifficulty } + + let lastBlock = chain.last! + let previousBlock = chain[chain.count - 2] + let timeSpent = Double(lastBlock.timestamp - previousBlock.timestamp) + + if timeSpent < targetBlockTime / 2 { + return min(lastBlock.difficulty + 1, maxDifficulty) + } else if timeSpent > targetBlockTime * 2 { + return max(lastBlock.difficulty - 1, minDifficulty) + } + + return lastBlock.difficulty } - - /** - Check validity of the blockchain. - */ - func chainValidity() { - var isChainValid = true - var corruptedBlock = Block() - - for i in 1...chain.count-1 { - if chain[i].previousHash != chain[i-1].hash { - isChainValid = false - corruptedBlock = chain[i] + + func submitTransaction(_ transaction: Transaction) -> Bool { + return memPool.addTransaction(transaction) + } + + func getBalance(address: String) -> Int { + return accountManager.getBalance(address) + } + + func chainValidity() -> Bool { + for i in 1.. Bool { + if validateTransaction(transaction) { + pendingTransactions.append(transaction) + return true + } + return false + } + + /** + Valide une transaction avant de l'ajouter au pool + */ + private func validateTransaction(_ transaction: Transaction) -> Bool { + // Basic validations + if transaction.amount <= 0 { + return false + } + + // Skip balance check for mining rewards + if transaction.type == "MINING_REWARD" { + return true + } + + // Check if sender has enough balance + return accountManager.canProcessTransaction(transaction) + } + + /** + Récupère un lot de transactions prêtes pour le prochain bloc + */ + func getTransactionsForBlock() -> [Transaction] { + var validTransactions: [Transaction] = [] + var remainingTransactions: [Transaction] = [] + + for transaction in pendingTransactions { + if validTransactions.count >= maxTransactionsPerBlock { + break + } + + if validateTransaction(transaction) { + validTransactions.append(transaction) + } else { + remainingTransactions.append(transaction) + } + } + + // Update pending transactions, removing the selected ones + pendingTransactions = remainingTransactions + pendingTransactions.dropFirst(validTransactions.count) + + return validTransactions + } + + /** + Vérifie si il y a des transactions en attente + */ + var hasPendingTransactions: Bool { + return !pendingTransactions.isEmpty + } + + /** + Retourne toutes les transactions en attente sans les retirer + */ + func getAllPendingTransactions() -> [Transaction] { + return pendingTransactions + } +} diff --git a/SwiftChain/main.swift b/SwiftChain/main.swift index 8b369a1..b883dff 100644 --- a/SwiftChain/main.swift +++ b/SwiftChain/main.swift @@ -10,35 +10,90 @@ import Foundation let blockchain = Blockchain() var command: String? +var currentMinerAddress = "MINER1" // Adresse par défaut du mineur -blockchain.createGenesisBlock(data: "") +print(""" +Blockchain CLI - Commandes disponibles: +- mine : Miner un nouveau bloc +- send : Envoyer des tokens +- balance : Voir le solde d'une adresse +- pending : Voir les transactions en attente +- validity : Vérifier la validité de la chaîne +- setminer : Changer l'adresse du mineur +- exit : Quitter +""") + +blockchain.createGenesisBlock() repeat { - print("Enter command:") - command = readLine() - - // Create a new block - if command == "create" { - print("data for the new block:") - let data = readLine() - blockchain.createBlock(data: data ?? "") - } - - // Create a lot of blocks - if command == "spam" { - for _ in 1...1000 { - blockchain.createBlock(data: "\((blockchain.chain.last?.index ?? 0)+1)") + print("\nEntrer une commande:") + command = readLine()?.lowercased() + + switch command { + case "mine": + if let block = blockchain.createBlock(minerAddress: currentMinerAddress) { + print("Bloc miné avec succès. Récompense envoyée à \(currentMinerAddress)") + print("Nouveau solde: \(blockchain.getBalance(address: currentMinerAddress))") + } else { + print("Échec du minage") } + + case "send": + print("Adresse de l'expéditeur:") + let sender = readLine() ?? "" + print("Adresse du destinataire:") + let receiver = readLine() ?? "" + print("Montant:") + if let amountStr = readLine(), let amount = Int(amountStr) { + let transaction = Transaction( + sender: sender, + receiver: receiver, + amount: amount, + type: "TRANSFER" + ) + + if blockchain.submitTransaction(transaction) { + print("Transaction ajoutée au mempool") + } else { + print("Transaction invalide") + } + } + + case "balance": + print("Entrer l'adresse:") + if let address = readLine() { + let balance = blockchain.getBalance(address: address) + print("Solde de \(address): \(balance)") + } + + case "setminer": + print("Nouvelle adresse du mineur:") + if let address = readLine() { + currentMinerAddress = address + print("Adresse du mineur mise à jour: \(currentMinerAddress)") + } + + case "pending": + let transactions = blockchain.memPool.getAllPendingTransactions() + print("Transactions en attente: \(transactions.count)") + for (index, tx) in transactions.enumerated() { + print(""" + \(index + 1). De: \(tx.sender) + À: \(tx.receiver) + Montant: \(tx.amount) + Type: \(tx.type) + """) + } + + case "validity": + let isValid = blockchain.chainValidity() + print("Chaîne valide: \(isValid)") + + case "exit": + print("Au revoir!") + + default: + print("Commande inconnue") } - - // Check validity of the blockchain - if command == "validity" { - blockchain.chainValidity() - } - - // Insert a corrupted block - if command == "corrupt" { - blockchain.insertCorruptedBlock() - } - + } while command != "exit"