Add blockchain sync between nodes

This commit is contained in:
Victor Bodinaud
2024-11-30 11:49:31 +01:00
parent 3884a2a0ca
commit 6d0777930d
4 changed files with 249 additions and 2 deletions

View File

@@ -9,7 +9,7 @@
import Foundation import Foundation
import CryptoSwift import CryptoSwift
class Block { class Block: Codable {
var hash: String var hash: String
var transactions: [Transaction] var transactions: [Transaction]
var previousHash: String var previousHash: String
@@ -19,6 +19,10 @@ class Block {
var difficulty: Int var difficulty: Int
var miner: String? // Adresse qui recevra la récompense var miner: String? // Adresse qui recevra la récompense
enum CodingKeys: String, CodingKey {
case hash, transactions, previousHash, index, nonce, timestamp, difficulty, miner
}
init(hash: String = "", transactions: [Transaction] = [], previousHash: String = "", init(hash: String = "", transactions: [Transaction] = [], previousHash: String = "",
index: Int = 0, nonce: Int = 0, timestamp: Int = 0, difficulty: Int = 4) { index: Int = 0, nonce: Int = 0, timestamp: Int = 0, difficulty: Int = 4) {
self.hash = hash self.hash = hash
@@ -30,6 +34,30 @@ class Block {
self.difficulty = difficulty self.difficulty = difficulty
} }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
hash = try container.decode(String.self, forKey: .hash)
transactions = try container.decode([Transaction].self, forKey: .transactions)
previousHash = try container.decode(String.self, forKey: .previousHash)
index = try container.decode(Int.self, forKey: .index)
nonce = try container.decode(Int.self, forKey: .nonce)
timestamp = try container.decode(Int.self, forKey: .timestamp)
difficulty = try container.decode(Int.self, forKey: .difficulty)
miner = try container.decodeIfPresent(String.self, forKey: .miner)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(hash, forKey: .hash)
try container.encode(transactions, forKey: .transactions)
try container.encode(previousHash, forKey: .previousHash)
try container.encode(index, forKey: .index)
try container.encode(nonce, forKey: .nonce)
try container.encode(timestamp, forKey: .timestamp)
try container.encode(difficulty, forKey: .difficulty)
try container.encodeIfPresent(miner, forKey: .miner)
}
func mineBlock() -> Double { func mineBlock() -> Double {
let startTime = Date() let startTime = Date()
let target = String(repeating: "0", count: difficulty) let target = String(repeating: "0", count: difficulty)

View File

@@ -116,4 +116,62 @@ class Blockchain {
} }
return true return true
} }
func validateChain(_ newChain: [Block]) -> Bool {
// Vérifier que la chaîne commence par notre genesis block
guard newChain.first?.hash == chain.first?.hash else {
print("Genesis block mismatch")
return false
}
// Vérifier chaque bloc
for i in 1..<newChain.count {
let block = newChain[i]
let previousBlock = newChain[i-1]
// Vérifier le chaînage
if block.previousHash != previousBlock.hash {
print("Invalid chain at block \(i): incorrect previous hash")
return false
}
// Vérifier la validité du bloc
if !block.isValid() {
print("Invalid chain at block \(i): invalid block hash")
return false
}
}
return true
}
func replaceChain(_ newChain: [Block]) -> Bool {
// Vérifier que la nouvelle chaîne est plus longue
guard newChain.count > chain.count else {
print("Received chain is not longer than current chain")
return false
}
// Vérifier la validité de la nouvelle chaîne
guard validateChain(newChain) else {
print("Received chain is invalid")
return false
}
// Sauvegarder l'ancienne chaîne au cas où
let oldChain = chain
// Essayer de traiter tous les blocs
for block in newChain {
if !accountManager.processBlock(block) {
print("Failed to process transactions in block \(block.index)")
chain = oldChain
return false
}
}
chain = newChain
print("Chain replaced successfully")
return true
}
} }

View File

@@ -94,4 +94,37 @@ class MemPool {
func getAllPendingTransactions() -> [Transaction] { func getAllPendingTransactions() -> [Transaction] {
return pendingTransactions return pendingTransactions
} }
/**
* Nettoie le mempool en retirant les transactions qui sont dans la chaîne
*/
func cleanupMempool(chain: [Block]) {
var remainingTransactions: [Transaction] = []
for pendingTx in pendingTransactions {
// On vérifie si la transaction est dans un des blocs
var isInChain = false
for block in chain {
if block.transactions.contains(where: { chainTx in
chainTx.sender == pendingTx.sender &&
chainTx.receiver == pendingTx.receiver &&
chainTx.amount == pendingTx.amount &&
chainTx.type == pendingTx.type
}) {
isInChain = true
break
}
}
// Si la transaction n'est pas dans la chaîne, on la garde
if !isInChain {
remainingTransactions.append(pendingTx)
}
}
// Mettre à jour les transactions en attente
print("Mempool cleanup: removed \(pendingTransactions.count - remainingTransactions.count) transactions")
pendingTransactions = remainingTransactions
}
} }

View File

@@ -24,6 +24,9 @@ class Node {
enum MessageType: String, Codable { enum MessageType: String, Codable {
case transaction = "TX" case transaction = "TX"
case blockchainRequest = "BC_REQ"
case blockchainResponse = "BC_RES"
case newBlock = "NEW_BLOCK"
} }
struct NetworkMessage: Codable { struct NetworkMessage: Codable {
@@ -152,21 +155,146 @@ class Node {
case .transaction: case .transaction:
let transaction = try JSONDecoder().decode(Transaction.self, from: message.data) let transaction = try JSONDecoder().decode(Transaction.self, from: message.data)
print("Transaction decoded: \(transaction.sender) -> \(transaction.receiver): \(transaction.amount)") print("Transaction decoded: \(transaction.sender) -> \(transaction.receiver): \(transaction.amount)")
if blockchain.submitTransaction(transaction) { if blockchain.submitTransaction(transaction) {
print("Transaction added to mempool successfully") print("Transaction added to mempool successfully")
} else { } else {
print("Failed to add transaction to mempool") print("Failed to add transaction to mempool")
} }
case .blockchainRequest:
// Envoyer notre blockchain au pair
sendBlockchain(to: connection)
case .blockchainResponse:
// Recevoir et traiter la blockchain
let receivedChain = try JSONDecoder().decode([Block].self, from: message.data)
handleReceivedBlockchain(receivedChain)
case .newBlock:
// Recevoir et traiter un nouveau bloc
let block = try JSONDecoder().decode(Block.self, from: message.data)
handleReceivedBlock(block)
} }
} catch { } catch {
print("Failed to decode received data: \(error)") print("Failed to decode received data: \(error)")
} }
} }
// Quand un pair se connecte, demander sa blockchain
private func handleNewConnection(_ connection: NWConnection) { private func handleNewConnection(_ connection: NWConnection) {
connection.start(queue: queue) connection.start(queue: queue)
peers.append(connection) peers.append(connection)
startReceiving(connection) startReceiving(connection)
// Demander la blockchain
requestBlockchain(from: connection)
}
private func requestBlockchain(from peer: NWConnection) {
do {
let message = NetworkMessage(type: .blockchainRequest, data: Data())
let messageData = try JSONEncoder().encode(message)
peer.send(content: messageData, completion: .contentProcessed { error in
if let error = error {
print("Failed to request blockchain: \(error)")
} else {
print("Blockchain request sent to: \(peer.endpoint)")
}
})
} catch {
print("Failed to encode blockchain request: \(error)")
}
}
private func sendBlockchain(to peer: NWConnection) {
do {
let chainData = try JSONEncoder().encode(blockchain.chain)
let message = NetworkMessage(type: .blockchainResponse, data: chainData)
let messageData = try JSONEncoder().encode(message)
peer.send(content: messageData, completion: .contentProcessed { error in
if let error = error {
print("Failed to send blockchain: \(error)")
} else {
print("Blockchain sent to: \(peer.endpoint)")
}
})
} catch {
print("Failed to encode blockchain: \(error)")
}
}
private func handleReceivedBlockchain(_ receivedChain: [Block]) {
print("Received blockchain with \(receivedChain.count) blocks")
// Vérifier si la chaîne reçue est plus longue et valide
if receivedChain.count > blockchain.chain.count {
print("Received chain is longer, validating...")
if blockchain.replaceChain(receivedChain) {
print("Blockchain updated successfully")
// Nettoyer le mempool des transactions qui sont maintenant dans la chaîne
blockchain.memPool.cleanupMempool(chain: receivedChain)
} else {
print("Failed to update blockchain")
}
} else {
print("Current chain is longer or equal, keeping it")
}
}
private func handleReceivedBlock(_ block: Block) {
print("Received new block: \(block.hash)")
// Vérifier que le bloc suit bien notre dernier bloc
guard let lastBlock = blockchain.chain.last else {
print("No existing chain")
return
}
if block.previousHash != lastBlock.hash {
print("Block does not connect to our chain")
return
}
if !block.isValid() {
print("Block is not valid")
return
}
// Traiter les transactions du bloc
if !blockchain.accountManager.processBlock(block) {
print("Failed to process block transactions")
return
}
// Ajouter le bloc à la chaîne
blockchain.chain.append(block)
print("New block added to chain")
// Propager le bloc aux autres pairs (sauf celui qui nous l'a envoyé)
broadcastBlock(block)
}
private func broadcastBlock(_ block: Block) {
do {
let blockData = try JSONEncoder().encode(block)
let message = NetworkMessage(type: .newBlock, data: blockData)
let messageData = try JSONEncoder().encode(message)
print("Broadcasting block to peers")
for peer in peers {
peer.send(content: messageData, completion: .contentProcessed { error in
if let error = error {
print("Failed to send block: \(error)")
} else {
print("Block sent successfully to: \(peer.endpoint)")
}
})
}
} catch {
print("Failed to encode block: \(error)")
}
} }
} }