✨ Add p2p
This commit is contained in:
@@ -32,7 +32,7 @@ class Blockchain {
|
||||
genesisBlock.previousHash = "0000"
|
||||
genesisBlock.index = 0
|
||||
genesisBlock.timestamp = Int(Date().timeIntervalSince1970)
|
||||
genesisBlock.mineBlock()
|
||||
let _ = genesisBlock.mineBlock()
|
||||
chain.append(genesisBlock)
|
||||
print("Genesis block created -- hash: \(genesisBlock.hash)")
|
||||
}
|
||||
|
||||
@@ -29,17 +29,22 @@ class MemPool {
|
||||
Valide une transaction avant de l'ajouter au pool
|
||||
*/
|
||||
private func validateTransaction(_ transaction: Transaction) -> Bool {
|
||||
// Basic validations
|
||||
// Vérifications basiques
|
||||
if transaction.amount <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip balance check for mining rewards
|
||||
// Pas besoin de vérifier la signature pour les récompenses de minage
|
||||
if transaction.type == "MINING_REWARD" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if sender has enough balance
|
||||
// Vérifier la signature
|
||||
if !transaction.isSignatureValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Vérifier le solde
|
||||
return accountManager.canProcessTransaction(transaction)
|
||||
}
|
||||
|
||||
|
||||
172
SwiftChain/Models/Node.swift
Normal file
172
SwiftChain/Models/Node.swift
Normal file
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// Node.swift
|
||||
// SwiftChain
|
||||
//
|
||||
// Created by Victor on 27/11/2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
class Node {
|
||||
private var peers: [NWConnection] = []
|
||||
private let port: NWEndpoint.Port = 8333 // Port standard
|
||||
private var listener: NWListener?
|
||||
private let blockchain: Blockchain
|
||||
|
||||
// Queue pour gérer les connexions de manière asynchrone
|
||||
private let queue = DispatchQueue(label: "com.blockchain.node")
|
||||
|
||||
init(blockchain: Blockchain) {
|
||||
self.blockchain = blockchain
|
||||
setupListener()
|
||||
}
|
||||
|
||||
enum MessageType: String, Codable {
|
||||
case transaction = "TX"
|
||||
}
|
||||
|
||||
struct NetworkMessage: Codable {
|
||||
let type: MessageType
|
||||
let data: Data
|
||||
}
|
||||
|
||||
// Configuration du listener
|
||||
private func setupListener() {
|
||||
let parameters = NWParameters.tcp
|
||||
do {
|
||||
listener = try NWListener(using: parameters, on: port)
|
||||
listener?.stateUpdateHandler = { [weak self] state in
|
||||
switch state {
|
||||
case .ready:
|
||||
print("Node is listening on port \(self?.port.rawValue ?? 0)")
|
||||
case .failed(let error):
|
||||
print("Listener failed with error: \(error)")
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
listener?.newConnectionHandler = { [weak self] connection in
|
||||
self?.handleNewConnection(connection)
|
||||
print("New incoming connection from: \(connection.endpoint)")
|
||||
}
|
||||
|
||||
listener?.start(queue: queue)
|
||||
} catch {
|
||||
print("Failed to create listener: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// Connexion à un pair
|
||||
func connectToPeer(host: String) {
|
||||
let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(host), port: port)
|
||||
let connection = NWConnection(to: endpoint, using: .tcp)
|
||||
|
||||
connection.stateUpdateHandler = { [weak self] state in
|
||||
switch state {
|
||||
case .ready:
|
||||
print("Connected to peer: \(host)")
|
||||
self?.peers.append(connection)
|
||||
self?.startReceiving(connection)
|
||||
case .failed(let error):
|
||||
print("Connection failed: \(error)")
|
||||
case .cancelled:
|
||||
print("Connection cancelled to: \(host)")
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
connection.start(queue: queue)
|
||||
}
|
||||
|
||||
// Méthode pour lister les pairs connectés
|
||||
func listPeers() {
|
||||
if peers.isEmpty {
|
||||
print("Aucun pair connecté")
|
||||
return
|
||||
}
|
||||
|
||||
print("\nPairs connectés (\(peers.count)):")
|
||||
for (index, peer) in peers.enumerated() {
|
||||
print("\(index + 1). \(peer.endpoint)")
|
||||
}
|
||||
}
|
||||
|
||||
// Propagation d'une transaction
|
||||
func broadcastTransaction(_ transaction: Transaction) {
|
||||
do {
|
||||
let transactionData = try JSONEncoder().encode(transaction)
|
||||
let message = NetworkMessage(type: .transaction, data: transactionData)
|
||||
let messageData = try JSONEncoder().encode(message)
|
||||
|
||||
print("Broadcasting transaction to \(peers.count) peers")
|
||||
|
||||
for peer in peers {
|
||||
peer.send(content: messageData, completion: .contentProcessed { error in
|
||||
if let error = error {
|
||||
print("Failed to send transaction: \(error)")
|
||||
} else {
|
||||
print("Transaction sent successfully to: \(peer.endpoint)")
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
print("Failed to encode transaction: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
// Réception des messages
|
||||
private func startReceiving(_ connection: NWConnection) {
|
||||
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] content, _, isComplete, error in
|
||||
if let data = content {
|
||||
self?.handleReceivedData(data, from: connection)
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
print("Receive error: \(error)")
|
||||
}
|
||||
|
||||
if !isComplete && error == nil {
|
||||
self?.startReceiving(connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Traitement d'une transaction reçue
|
||||
private func handleReceivedTransaction(_ transaction: Transaction) {
|
||||
if blockchain.submitTransaction(transaction) {
|
||||
// Propager aux autres pairs si la transaction est valide
|
||||
broadcastTransaction(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleReceivedData(_ data: Data, from connection: NWConnection) {
|
||||
do {
|
||||
print("Received data of size: \(data.count) bytes")
|
||||
let message = try JSONDecoder().decode(NetworkMessage.self, from: data)
|
||||
print("Message decoded successfully, type: \(message.type)")
|
||||
|
||||
switch message.type {
|
||||
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")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Failed to decode received data: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func handleNewConnection(_ connection: NWConnection) {
|
||||
connection.start(queue: queue)
|
||||
peers.append(connection)
|
||||
startReceiving(connection)
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,13 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class Transaction {
|
||||
var sender: String
|
||||
var receiver: String
|
||||
var amount: Int
|
||||
var type: String
|
||||
class Transaction: Codable {
|
||||
let sender: String
|
||||
let receiver: String
|
||||
let amount: Int
|
||||
let type: String
|
||||
var signature: Data?
|
||||
var senderPublicKey: Data?
|
||||
|
||||
init(sender: String, receiver: String, amount: Int, type: String) {
|
||||
self.sender = sender
|
||||
@@ -19,4 +21,53 @@ class Transaction {
|
||||
self.amount = amount
|
||||
self.type = type
|
||||
}
|
||||
|
||||
// Pour encoder/décoder les Data optionnels
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case sender, receiver, amount, type, signature, senderPublicKey
|
||||
}
|
||||
|
||||
// Données à signer
|
||||
func messageToSign() -> Data {
|
||||
return "\(sender)\(receiver)\(amount)\(type)".data(using: .utf8)!
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
sender = try container.decode(String.self, forKey: .sender)
|
||||
receiver = try container.decode(String.self, forKey: .receiver)
|
||||
amount = try container.decode(Int.self, forKey: .amount)
|
||||
type = try container.decode(String.self, forKey: .type)
|
||||
signature = try container.decodeIfPresent(Data.self, forKey: .signature)
|
||||
senderPublicKey = try container.decodeIfPresent(Data.self, forKey: .senderPublicKey)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(sender, forKey: .sender)
|
||||
try container.encode(receiver, forKey: .receiver)
|
||||
try container.encode(amount, forKey: .amount)
|
||||
try container.encode(type, forKey: .type)
|
||||
try container.encodeIfPresent(signature, forKey: .signature)
|
||||
try container.encodeIfPresent(senderPublicKey, forKey: .senderPublicKey)
|
||||
}
|
||||
|
||||
// Vérifier la validité de la signature
|
||||
func isSignatureValid() -> Bool {
|
||||
guard let signature = signature,
|
||||
let publicKeyData = senderPublicKey else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Pas besoin de vérifier la signature pour les récompenses de minage
|
||||
if type == "MINING_REWARD" {
|
||||
return true
|
||||
}
|
||||
|
||||
return Wallet.verifySignature(
|
||||
for: self,
|
||||
signature: signature,
|
||||
publicKeyData: publicKeyData
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
47
SwiftChain/Models/Wallet.swift
Normal file
47
SwiftChain/Models/Wallet.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Wallet.swift
|
||||
// SwiftChain
|
||||
//
|
||||
// Created by Victor on 27/11/2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
class Wallet {
|
||||
private let privateKey: Curve25519.Signing.PrivateKey
|
||||
let publicKey: Curve25519.Signing.PublicKey
|
||||
let address: String
|
||||
|
||||
init() {
|
||||
// Générer une nouvelle paire de clés
|
||||
privateKey = Curve25519.Signing.PrivateKey()
|
||||
publicKey = privateKey.publicKey
|
||||
|
||||
// Créer une adresse au format swift_(hash)
|
||||
let pubKeyData = publicKey.rawRepresentation
|
||||
let hash = SHA256.hash(data: pubKeyData)
|
||||
let hashString = hash.compactMap { String(format: "%02x", $0) }.joined()
|
||||
address = "swift_" + hashString.prefix(40)
|
||||
}
|
||||
|
||||
// Signer une transaction
|
||||
func signTransaction(_ transaction: Transaction) -> Data? {
|
||||
let messageData = transaction.messageToSign()
|
||||
return try? privateKey.signature(for: messageData)
|
||||
}
|
||||
|
||||
// Vérifier une signature
|
||||
static func verifySignature(for transaction: Transaction, signature: Data, publicKeyData: Data) -> Bool {
|
||||
guard let publicKey = try? Curve25519.Signing.PublicKey(rawRepresentation: publicKeyData) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return (publicKey.isValidSignature(signature, for: transaction.messageToSign()))
|
||||
}
|
||||
|
||||
// Obtenir la clé publique en format Data
|
||||
func getPublicKeyData() -> Data {
|
||||
return publicKey.rawRepresentation
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,10 @@
|
||||
import Foundation
|
||||
|
||||
let blockchain = Blockchain()
|
||||
let node = Node(blockchain: blockchain)
|
||||
var command: String?
|
||||
var currentMinerAddress = "MINER1" // Adresse par défaut du mineur
|
||||
var wallets: [String: Wallet] = [:]
|
||||
|
||||
print("""
|
||||
Blockchain CLI - Commandes disponibles:
|
||||
@@ -20,6 +22,8 @@ Blockchain CLI - Commandes disponibles:
|
||||
- pending : Voir les transactions en attente
|
||||
- validity : Vérifier la validité de la chaîne
|
||||
- setminer : Changer l'adresse du mineur
|
||||
- connect <host> : Se connecter à un pair
|
||||
- peers : Liste des pairs connectés
|
||||
- exit : Quitter
|
||||
""")
|
||||
|
||||
@@ -30,6 +34,15 @@ repeat {
|
||||
command = readLine()?.lowercased()
|
||||
|
||||
switch command {
|
||||
case "connect":
|
||||
print("Entrez l'adresse du pair (ex: 192.168.1.100):")
|
||||
if let host = readLine() {
|
||||
node.connectToPeer(host: host)
|
||||
}
|
||||
|
||||
case "peers":
|
||||
node.listPeers()
|
||||
|
||||
case "mine":
|
||||
if let block = blockchain.createBlock(minerAddress: currentMinerAddress) {
|
||||
print("Bloc miné avec succès. Récompense envoyée à \(currentMinerAddress)")
|
||||
@@ -38,27 +51,6 @@ repeat {
|
||||
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() {
|
||||
@@ -78,11 +70,11 @@ repeat {
|
||||
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)
|
||||
""")
|
||||
\(index + 1). De: \(tx.sender)
|
||||
À: \(tx.receiver)
|
||||
Montant: \(tx.amount)
|
||||
Type: \(tx.type)
|
||||
""")
|
||||
}
|
||||
|
||||
case "validity":
|
||||
@@ -92,6 +84,48 @@ repeat {
|
||||
case "exit":
|
||||
print("Au revoir!")
|
||||
|
||||
// Ajoutons une nouvelle commande pour créer un wallet
|
||||
case "createwallet":
|
||||
let wallet = Wallet()
|
||||
wallets[wallet.address] = wallet
|
||||
print("Nouveau wallet créé!")
|
||||
print("Adresse: \(wallet.address)")
|
||||
|
||||
// Modifions la commande send
|
||||
case "send":
|
||||
print("Votre adresse (wallet):")
|
||||
guard let senderAddress = readLine(),
|
||||
let wallet = wallets[senderAddress]
|
||||
else {
|
||||
print("Wallet non trouvé")
|
||||
break
|
||||
}
|
||||
|
||||
print("Adresse du destinataire:")
|
||||
guard let receiverAddress = readLine() else { break }
|
||||
|
||||
print("Montant:")
|
||||
guard let amountStr = readLine(),
|
||||
let amount = Int(amountStr) else { break }
|
||||
|
||||
let transaction = Transaction(
|
||||
sender: senderAddress,
|
||||
receiver: receiverAddress,
|
||||
amount: amount,
|
||||
type: "TRANSFER"
|
||||
)
|
||||
|
||||
// Signer la transaction
|
||||
transaction.senderPublicKey = wallet.getPublicKeyData()
|
||||
transaction.signature = wallet.signTransaction(transaction)
|
||||
|
||||
if blockchain.submitTransaction(transaction) {
|
||||
node.broadcastTransaction(transaction)
|
||||
print("Transaction propagée au réseau!")
|
||||
} else {
|
||||
print("Erreur lors de l'envoi de la transaction")
|
||||
}
|
||||
|
||||
default:
|
||||
print("Commande inconnue")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user