✨Add mempool & transactions
This commit is contained in:
72
SwiftChain/Models/AccountManager.swift
Normal file
72
SwiftChain/Models/AccountManager.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
/**
|
init(hash: String = "", transactions: [Transaction] = [], previousHash: String = "",
|
||||||
Initialize a block with the provided parts and specifications.
|
index: Int = 0, nonce: Int = 0, timestamp: Int = 0, difficulty: Int = 4) {
|
||||||
|
self.hash = hash
|
||||||
- parameters:
|
self.transactions = transactions
|
||||||
- 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
|
|
||||||
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ 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.
|
||||||
@@ -17,14 +27,12 @@ class 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)")
|
||||||
}
|
}
|
||||||
@@ -35,54 +43,77 @@ class 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
|
|
||||||
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)")
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private func calculateNewDifficulty() -> Int {
|
||||||
Insert a corrupted block in the blockhain.
|
guard chain.count >= 2 else { return minDifficulty }
|
||||||
(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)")
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
func submitTransaction(_ transaction: Transaction) -> Bool {
|
||||||
Check validity of the blockchain.
|
return memPool.addTransaction(transaction)
|
||||||
*/
|
}
|
||||||
func chainValidity() {
|
|
||||||
var isChainValid = true
|
|
||||||
var corruptedBlock = Block()
|
|
||||||
|
|
||||||
for i in 1...chain.count-1 {
|
func getBalance(address: String) -> Int {
|
||||||
if chain[i].previousHash != chain[i-1].hash {
|
return accountManager.getBalance(address)
|
||||||
isChainValid = false
|
}
|
||||||
corruptedBlock = chain[i]
|
|
||||||
|
func chainValidity() -> Bool {
|
||||||
|
for i in 1..<chain.count {
|
||||||
|
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)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
84
SwiftChain/Models/MemPool.swift
Normal file
84
SwiftChain/Models/MemPool.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check validity of the blockchain
|
case "send":
|
||||||
if command == "validity" {
|
print("Adresse de l'expéditeur:")
|
||||||
blockchain.chainValidity()
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
// Insert a corrupted block
|
if blockchain.submitTransaction(transaction) {
|
||||||
if command == "corrupt" {
|
print("Transaction ajoutée au mempool")
|
||||||
blockchain.insertCorruptedBlock()
|
} 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
} while command != "exit"
|
} while command != "exit"
|
||||||
|
|||||||
Reference in New Issue
Block a user