✨ Add blockchain sync between nodes
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import CryptoSwift
|
||||
|
||||
class Block {
|
||||
class Block: Codable {
|
||||
var hash: String
|
||||
var transactions: [Transaction]
|
||||
var previousHash: String
|
||||
@@ -19,6 +19,10 @@ class Block {
|
||||
var difficulty: Int
|
||||
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 = "",
|
||||
index: Int = 0, nonce: Int = 0, timestamp: Int = 0, difficulty: Int = 4) {
|
||||
self.hash = hash
|
||||
@@ -30,6 +34,30 @@ class Block {
|
||||
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 {
|
||||
let startTime = Date()
|
||||
let target = String(repeating: "0", count: difficulty)
|
||||
|
||||
@@ -116,4 +116,62 @@ class Blockchain {
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +94,37 @@ class MemPool {
|
||||
func getAllPendingTransactions() -> [Transaction] {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ class Node {
|
||||
|
||||
enum MessageType: String, Codable {
|
||||
case transaction = "TX"
|
||||
case blockchainRequest = "BC_REQ"
|
||||
case blockchainResponse = "BC_RES"
|
||||
case newBlock = "NEW_BLOCK"
|
||||
}
|
||||
|
||||
struct NetworkMessage: Codable {
|
||||
@@ -152,21 +155,146 @@ class Node {
|
||||
case .transaction:
|
||||
let transaction = try JSONDecoder().decode(Transaction.self, from: message.data)
|
||||
print("Transaction decoded: \(transaction.sender) -> \(transaction.receiver): \(transaction.amount)")
|
||||
|
||||
if blockchain.submitTransaction(transaction) {
|
||||
print("Transaction added to mempool successfully")
|
||||
} else {
|
||||
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 {
|
||||
print("Failed to decode received data: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// Quand un pair se connecte, demander sa blockchain
|
||||
private func handleNewConnection(_ connection: NWConnection) {
|
||||
connection.start(queue: queue)
|
||||
peers.append(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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user