Add mempool & transactions

This commit is contained in:
Victor Bodinaud
2024-11-27 19:42:01 +01:00
parent 5db6a5bfe2
commit 9905bf2964
5 changed files with 371 additions and 107 deletions

View File

@@ -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
}
}

View File

@@ -11,49 +11,71 @@ import CryptoSwift
class Block { class Block {
var hash: String var hash: String
var data: String var transactions: [Transaction]
var previousHash: String var previousHash: String
var index: Int var index: Int
var nonce: Int var nonce: Int
var timestamp: Int var timestamp: Int
var difficulty: Int
/** var miner: String? // Adresse qui recevra la récompense
Initialize a block with the provided parts and specifications.
init(hash: String = "", transactions: [Transaction] = [], previousHash: String = "",
- parameters: index: Int = 0, nonce: Int = 0, timestamp: Int = 0, difficulty: Int = 4) {
- hash: The hash of the block self.hash = hash
- data: The data of the block self.transactions = transactions
- 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
self.previousHash = previousHash self.previousHash = previousHash
self.index = index self.index = index
self.nonce = nonce self.nonce = nonce
self.timestamp = timestamp 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. Génère le hash en incluant toutes les transactions
- returns: The hash of the block
*/ */
func generateHash() -> String { 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 Calcule le montant total des transactions dans le bloc
- returns: The timestamp of the block
*/ */
func generateTimestamp() -> Int { func getTotalAmount() -> Int {
return Int(Date().timeIntervalSince1970) 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
}
} }
} }

View File

@@ -10,79 +10,110 @@ import Foundation
class Blockchain { class Blockchain {
var chain = [Block]() 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. Initialize the first block of the blockchain.
- Parameters: - Parameters:
- data: The datas of the block - data: The datas of the block
*/ */
func createGenesisBlock(data: String) { func createGenesisBlock() {
let genesisBlock = Block() let genesisBlock = Block()
genesisBlock.data = data
genesisBlock.previousHash = "0000" genesisBlock.previousHash = "0000"
genesisBlock.index = 0 genesisBlock.index = 0
genesisBlock.nonce = 0 genesisBlock.timestamp = Int(Date().timeIntervalSince1970)
genesisBlock.timestamp = genesisBlock.generateTimestamp() genesisBlock.mineBlock()
genesisBlock.hash = genesisBlock.generateHash()
chain.append(genesisBlock) chain.append(genesisBlock)
print("Genesis block created -- hash: \(genesisBlock.hash)") print("Genesis block created -- hash: \(genesisBlock.hash)")
} }
/** /**
Initialize a new block of the blockchain. Initialize a new block of the blockchain.
- Parameters: - Parameters:
- data: The datas of the block - data: The datas of the block
*/ */
func createBlock(data: String) { func createBlock(minerAddress: String) -> Block? {
let newBlock = Block() guard let lastBlock = chain.last else { return nil }
newBlock.data = data
newBlock.previousHash = chain.last!.hash // Récupérer les transactions en attente
newBlock.index = chain.count var transactions = memPool.getTransactionsForBlock()
newBlock.nonce = 0
newBlock.timestamp = newBlock.generateTimestamp() // Ajouter la récompense de minage
newBlock.hash = newBlock.generateHash() let miningReward = Transaction(sender: "SYSTEM", receiver: minerAddress, amount: 50, type: "MINING_REWARD")
chain.append(newBlock) transactions.append(miningReward)
print("-- Block \(newBlock.index) created --\n hash: \(newBlock.hash)\n previous hash: \(newBlock.previousHash)\n data: \(newBlock.data)") 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
} }
/** private func calculateNewDifficulty() -> Int {
Insert a corrupted block in the blockhain. guard chain.count >= 2 else { return minDifficulty }
(for testing purpose)
*/ let lastBlock = chain.last!
func insertCorruptedBlock() { let previousBlock = chain[chain.count - 2]
let newCorruptedBlock = Block() let timeSpent = Double(lastBlock.timestamp - previousBlock.timestamp)
newCorruptedBlock.data = "Corrupted block"
newCorruptedBlock.previousHash = "1234567890" if timeSpent < targetBlockTime / 2 {
newCorruptedBlock.index = chain.count return min(lastBlock.difficulty + 1, maxDifficulty)
newCorruptedBlock.nonce = 0 } else if timeSpent > targetBlockTime * 2 {
newCorruptedBlock.timestamp = newCorruptedBlock.generateTimestamp() return max(lastBlock.difficulty - 1, minDifficulty)
newCorruptedBlock.hash = newCorruptedBlock.generateHash() }
chain.append(newCorruptedBlock)
return lastBlock.difficulty
print("-- Corrupted block \(newCorruptedBlock.index) created --\n hash: \(newCorruptedBlock.hash)\n previous hash: \(newCorruptedBlock.previousHash)\n data: \(newCorruptedBlock.data)")
} }
/** func submitTransaction(_ transaction: Transaction) -> Bool {
Check validity of the blockchain. return memPool.addTransaction(transaction)
*/ }
func chainValidity() {
var isChainValid = true func getBalance(address: String) -> Int {
var corruptedBlock = Block() return accountManager.getBalance(address)
}
for i in 1...chain.count-1 {
if chain[i].previousHash != chain[i-1].hash { func chainValidity() -> Bool {
isChainValid = false for i in 1..<chain.count {
corruptedBlock = chain[i] let currentBlock = chain[i]
let previousBlock = chain[i-1]
if !currentBlock.isValid() || currentBlock.previousHash != previousBlock.hash {
print("Chain invalid at block \(i)")
return false
} }
} }
return true
print("Chain is valid : \(isChainValid)")
if !isChainValid {
print("Corrupted block is : \(corruptedBlock.index)")
}
} }
} }

View File

@@ -0,0 +1,84 @@
//
// MemPool.swift
// SwiftChain
//
// Created by Victor on 27/11/2024.
//
class MemPool {
private var pendingTransactions: [Transaction] = []
private let maxTransactionsPerBlock: Int = 10
private let accountManager: AccountManager
init(accountManager: AccountManager) {
self.accountManager = accountManager
}
/**
Ajoute une transaction au mempool après validation
*/
func addTransaction(_ transaction: Transaction) -> 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
}
}

View File

@@ -10,35 +10,90 @@ import Foundation
let blockchain = Blockchain() let blockchain = Blockchain()
var command: String? 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 { repeat {
print("Enter command:") print("\nEntrer une commande:")
command = readLine() command = readLine()?.lowercased()
// Create a new block switch command {
if command == "create" { case "mine":
print("data for the new block:") if let block = blockchain.createBlock(minerAddress: currentMinerAddress) {
let data = readLine() print("Bloc miné avec succès. Récompense envoyée à \(currentMinerAddress)")
blockchain.createBlock(data: data ?? "") print("Nouveau solde: \(blockchain.getBalance(address: currentMinerAddress))")
} } else {
print("Échec du minage")
// Create a lot of blocks
if command == "spam" {
for _ in 1...1000 {
blockchain.createBlock(data: "\((blockchain.chain.last?.index ?? 0)+1)")
} }
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" } while command != "exit"