Blocs, chaines et consensus#

Les deux chapitres précédents ont introduit les motivations de la blockchain et les primitives cryptographiques qui la rendent possible. Il est temps de passer à l’architecture concrète : comment les transactions sont-elles regroupées, ordonnées et validées par un réseau de machines qui ne se font pas confiance ? C’est la question du consensus, le problème central de tout système distribué décentralisé.

Ce chapitre construit la reponse en partant de la brique la plus élémentaire — le bloc — puis en assemblant les blocs en une chaine immuable. Nous formalisons ensuite le problème des généraux byzantins, qui capture l’essence de la difficulté, avant de détailler les deux grandes familles de mécanismes de consensus : la preuve de travail (PoW) et la preuve d’enjeu (PoS). Chaque concept est accompagné de simulations Python pour ancrer l’intuition. Le chapitre se termine par un survol des variantes modernes — dont la Proof of History de Solana, qui fera l’objet du chapitre suivant.

Le lecteur qui maitrise ces notions disposera du cadre conceptuel nécessaire pour comprendre pourquoi Solana a fait les choix architecturaux qui la distingue des autres blockchains.

Structure d’un bloc#

Définition 20 (Bloc)

Un bloc est l’unité fondamentale de stockage dans une blockchain. Il se compose de deux parties :

  1. L’en-tête (header), qui contient les métadonnées :

    • le hash du bloc précédent (previous hash), qui lie ce bloc au précédent ;

    • la racine de Merkle (Merkle root), un hash unique résumant l’ensemble des transactions du bloc ;

    • le nonce, un entier arbitraire utilisé par les mécanismes de consensus ;

    • l”horodatage (timestamp), la date de création du bloc.

  2. Le corps (body), qui contient la liste ordonnée des transactions incluses dans le bloc.

Remarque 16

La racine de Merkle est un hash calculé à partir d’un arbre binaire de hachage (arbre de Merkle). Elle permet de vérifier l’intégrité de n’importe quelle transaction du bloc sans avoir à re-hacher toutes les autres : il suffit de fournir le chemin de Merkle (les hashes frères le long de la branche). Cette propriété est cruciale pour les clients légers qui ne téléchargent pas l’intégralité de la blockchain.

La visualisation suivante représente la structure interne d’un bloc, avec les champs de l’en-tête et le corps contenant les transactions.

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, ax = plt.subplots(figsize=(10, 7))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title("Structure d'un bloc", fontsize=14, fontweight="bold", pad=15)

# --- Couleurs ---
c_header = "#4C72B0"
c_field = "#64B5F6"
c_body = "#55A868"
c_tx = "#81C784"
c_border = "#333333"

# Cadre global du bloc
bloc = mpatches.FancyBboxPatch((0.5, 0.5), 9, 9, boxstyle="round,pad=0.15",
                                facecolor="#F5F5F5", edgecolor=c_border, linewidth=2)
ax.add_patch(bloc)
ax.text(5, 9.7, "Bloc #N", ha="center", va="center", fontsize=13, fontweight="bold")

# --- En-tête ---
header_bg = mpatches.FancyBboxPatch((1, 5.5), 8, 3.5, boxstyle="round,pad=0.1",
                                     facecolor=c_header, edgecolor=c_border, linewidth=1.5, alpha=0.15)
ax.add_patch(header_bg)
ax.text(5, 8.7, "En-tete (Header)", ha="center", va="center", fontsize=12,
        fontweight="bold", color=c_header)

fields = [
    ("Hash du bloc precedent", 7.9),
    ("Racine de Merkle", 7.1),
    ("Nonce", 6.3),
    ("Horodatage (timestamp)", 5.7),
]
for label, y in fields:
    rect = mpatches.FancyBboxPatch((1.5, y - 0.25), 7, 0.5, boxstyle="round,pad=0.05",
                                    facecolor=c_field, edgecolor=c_border, linewidth=1, alpha=0.7)
    ax.add_patch(rect)
    ax.text(5, y, label, ha="center", va="center", fontsize=10, fontweight="bold", color="white")

# --- Corps ---
body_bg = mpatches.FancyBboxPatch((1, 1), 8, 3.8, boxstyle="round,pad=0.1",
                                   facecolor=c_body, edgecolor=c_border, linewidth=1.5, alpha=0.15)
ax.add_patch(body_bg)
ax.text(5, 4.5, "Corps (Body) -- Transactions", ha="center", va="center", fontsize=12,
        fontweight="bold", color=c_body)

txs = ["Tx 1 : Alice -> Bob  2 SOL", "Tx 2 : Bob -> Carol  0.5 SOL",
       "Tx 3 : Carol -> Dave  1 SOL", "Tx 4 : Dave -> Alice  0.3 SOL"]
for i, tx in enumerate(txs):
    y = 3.6 - i * 0.7
    rect = mpatches.FancyBboxPatch((1.5, y - 0.25), 7, 0.5, boxstyle="round,pad=0.05",
                                    facecolor=c_tx, edgecolor=c_border, linewidth=1, alpha=0.7)
    ax.add_patch(rect)
    ax.text(5, y, tx, ha="center", va="center", fontsize=9.5, fontweight="bold", color="#1B5E20")

plt.show()
_images/a59a12a8070336798f034baf5a9e97a9bb7bbecaef8cc9021d29698be0ae7d44.png

La chaine#

Définition 21 (Blockchain (chaine de blocs))

Une blockchain est une liste chainée de blocs où chaque bloc contient, dans son en-tête, le hash cryptographique du bloc précédent. Cette liaison forme une sequence ordonnée et inaltérable :

\[B_0 \leftarrow B_1 \leftarrow B_2 \leftarrow \cdots \leftarrow B_n\]

Le premier bloc \(B_0\) est appele bloc genèse (genesis block). La chaine est valide si et seulement si, pour tout \(i \geq 1\), le champ previous_hash de \(B_i\) est égal au hash effectif de \(B_{i-1}\).

Construisons une mini-blockchain en Python pour concrétiser cette définition. On utilise un dataclass pour representer un bloc, avec un hash SHA-256 calculé à partir de ses champs.

Hide code cell source

import hashlib
import time
from dataclasses import dataclass, field


@dataclass
class Block:
    """Un bloc simplifié pour notre mini-blockchain."""
    index: int
    timestamp: float
    data: str
    previous_hash: str
    nonce: int = 0
    hash: str = field(default="", init=False)

    def __post_init__(self):
        self.hash = self.compute_hash()

    def compute_hash(self) -> str:
        content = f"{self.index}{self.timestamp}{self.data}{self.previous_hash}{self.nonce}"
        return hashlib.sha256(content.encode()).hexdigest()


def create_genesis_block() -> Block:
    """Cree le bloc genese (index 0)."""
    return Block(index=0, timestamp=time.time(), data="Bloc genèse", previous_hash="0" * 64)


def add_block(chain: list[Block], data: str) -> Block:
    """Ajoute un nouveau bloc à la chaine."""
    previous = chain[-1]
    new_block = Block(
        index=previous.index + 1,
        timestamp=time.time(),
        data=data,
        previous_hash=previous.hash,
    )
    chain.append(new_block)
    return new_block


def validate_chain(chain: list[Block]) -> bool:
    """Verifie l'intégrité de la chaine."""
    for i in range(1, len(chain)):
        current = chain[i]
        previous = chain[i - 1]
        # Verifier que le hash stocke est correct
        if current.hash != current.compute_hash():
            print(f"Bloc {i} : hash invalide")
            return False
        # Verifier le lien avec le bloc precedent
        if current.previous_hash != previous.hash:
            print(f"Bloc {i} : lien brisé avec le bloc {i-1}")
            return False
    return True


# --- Construction d'une chaine de 5 blocs ---
blockchain: list[Block] = [create_genesis_block()]

transactions = [
    "Alice envoie 2 SOL à Bob",
    "Bob envoie 0.5 SOL à Carol",
    "Carol envoie 1 SOL à Dave",
    "Dave envoie 0.3 SOL à Alice",
]

for tx in transactions:
    add_block(blockchain, tx)

# Affichage
for block in blockchain:
    print(f"Bloc {block.index}")
    print(f"  Data          : {block.data}")
    print(f"  Previous hash : {block.previous_hash[:16]}...")
    print(f"  Hash          : {block.hash[:16]}...")
    print()

print(f"Chaine valide : {validate_chain(blockchain)}")
Bloc 0
  Data          : Bloc genèse
  Previous hash : 0000000000000000...
  Hash          : 62bec339305d50d5...

Bloc 1
  Data          : Alice envoie 2 SOL à Bob
  Previous hash : 62bec339305d50d5...
  Hash          : 7203af47af94e1ab...

Bloc 2
  Data          : Bob envoie 0.5 SOL à Carol
  Previous hash : 7203af47af94e1ab...
  Hash          : cafb79ee92d9bc54...

Bloc 3
  Data          : Carol envoie 1 SOL à Dave
  Previous hash : cafb79ee92d9bc54...
  Hash          : a4e6340bb31f8363...

Bloc 4
  Data          : Dave envoie 0.3 SOL à Alice
  Previous hash : a4e6340bb31f8363...
  Hash          : 54e7ad69adab7905...

Chaine valide : True

Remarque 17

L’immutabilité de la blockchain découle directement de la structure de chainage par hash. Si un attaquant modifie une transaction dans le bloc \(B_k\), le hash de \(B_k\) change. Or ce hash est référencé dans l’en-tête de \(B_{k+1}\), dont le hash change a son tour, et ainsi de suite en cascade jusqu’au dernier bloc de la chaine. Pour que la falsification soit acceptée, l’attaquant devrait recalculer les hashes de tous les blocs suivants et convaincre la majorité du réseau d’adopter sa version — une tâche rendue impraticable par les mécanismes de consensus.

Exemple 9

Reprenons notre chaine de 5 blocs. Si on modifie le contenu du bloc 2 (par exemple en changeant le montant d’une transaction), son hash change, ce qui invalide le champ previous_hash du bloc 3, puis du bloc 4, etc. La fonction validate_chain détecte immédiatement la corruption. C’est exactement ce mécanisme qui rend la blockchain résistante a la falsification.

Le problème des généraux byzantins#

Avant d’étudier les mécanismes de consensus, il faut comprendre le problème fondamental qu’ils résolvent. Ce problème a été formalisé en 1982 par Lamport, Shostak et Pease sous le nom de problème des généraux byzantins.

Définition 22 (Problème des généraux byzantins)

Le problème des généraux byzantins est un problème de théorie des systèmes distribués. Plusieurs généraux, chacun commandant une division de l’armée, encerclent une ville ennemie. Ils doivent se coordonner pour attaquer ou battre en retraite simultanément (une attaque partielle mènerait à la defaite). Les généraux communiquent par messagers, mais certains généraux peuvent être des traitres : ils envoient des messages contradictoires pour semer la confusion.

Le problème consiste a concevoir un protocole permettant aux généraux loyaux de parvenir a une décision commune, malgrè la présence de traitres.

Remarque 18

Dans le contexte d’une blockchain, les généraux sont les noeuds du réseau, les messagers sont les messages échangés sur le réseau, et les traitres sont les noeuds malveillants ou défaillants. La « décision commune » est l’accord sur l’état du registre (quelles transactions sont valides, dans quel ordre).

Définition 23 (Tolérance aux fautes byzantines (BFT))

Un systeme distribué est dit tolérant aux fautes byzantines (Byzantine Fault Tolerant, BFT) s’il peut fonctionner correctement même lorsqu’une fraction des noeuds se comporte de manière arbitraire (pannes, mensonges, attaques). Le résultat fondamental de Lamport et al. établit que la tolérance aux fautes byzantines est possible si et seulement si le nombre de noeuds défaillants \(f\) satisfait :

\[n > 3f\]

ou \(n\) est le nombre total de noeuds. Autrement dit, le systeme tolère au plus \(\lfloor (n-1)/3 \rfloor\) noeuds malveillants, ce qui impose que plus des deux tiers des noeuds soient honnêtes.

Exemple 10

Considerons un réseau de \(n = 4\) noeuds. D’après la condition \(n > 3f\), on a \(f < 4/3\), soit \(f \leq 1\). Le système tolère au plus un noeud malveillant. Si deux des quatre noeuds sont corrompus, il est impossible de garantir un consensus fiable — les deux noeuds honnêtes ne peuvent pas distinguer les messages authentiques des messages falsifiés.

Proof of Work (PoW)#

La preuve de travail est le premier mécanisme de consensus utilisé dans une blockchain publique, introduit par Satoshi Nakamoto dans le livre blanc de Bitcoin (2008). L’idée est élégante : pour proposer un nouveau bloc, un noeud doit résoudre un problème calculatoire difficile mais dont la solution est facile à vérifier.

Définition 24 (Preuve de travail (Proof of Work))

La preuve de travail est un mécanisme de consensus dans lequel un noeud (appelé mineur) doit trouver un entier \(\text{nonce}\) tel que le hash du bloc, incluant ce nonce, satisfasse une condition de difficulté :

\[\text{hash}(\text{header} \| \text{nonce}) < \text{cible}\]

\(\text{cible}\) est un seuil fixé par le protocole. Plus la cible est basse, plus le nombre de zéros en tête du hash doit être grand, et plus la recherche du nonce est coûteuse en calculs. Le premier mineur à trouver un nonce valide diffuse son bloc au réseau ; les autres noeuds vérifient la solution (en un seul calcul de hash) et ajoutent le bloc à leur copie de la chaine.

Simulons la recherche de nonces pour differents niveaux de difficulté. La difficulté est mesurée par le nombre de zéros en tête du hash.

Hide code cell source

import hashlib
import time
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)


def mine_block(data: str, difficulty: int) -> tuple[int, float]:
    """Cherche un nonce tel que le hash commence par `difficulty` zeros.

    Retourne (nonce, duree_en_secondes).
    """
    prefix = "0" * difficulty
    nonce = 0
    start = time.perf_counter()
    while True:
        text = f"{data}{nonce}"
        h = hashlib.sha256(text.encode()).hexdigest()
        if h.startswith(prefix):
            return nonce, time.perf_counter() - start
        nonce += 1


# --- Mesure pour differentes difficultes ---
data = "Bloc de test : Alice envoie 1 SOL à Bob | timestamp=1700000000"
difficulties = [1, 2, 3, 4, 5]
results = []

for d in difficulties:
    nonce, duration = mine_block(data, d)
    results.append((d, nonce, duration))
    print(f"Difficulte {d} : nonce = {nonce:>8d}, temps = {duration:.4f} s")

# --- Visualisation ---
fig, ax = plt.subplots(figsize=(8, 5))
ds = [r[0] for r in results]
ts = [r[2] for r in results]
bars = ax.bar(ds, ts, color=sns.color_palette("muted"), edgecolor="white", linewidth=1.5)

ax.set_xlabel("Difficulté (nombre de zéros en tête)")
ax.set_ylabel("Temps de minage (secondes)")
ax.set_title("Temps de minage en fonction de la difficulté", fontweight="bold")
ax.set_xticks(ds)

for bar, t in zip(bars, ts):
    ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + max(ts) * 0.02,
            f"{t:.3f}s", ha="center", va="bottom", fontsize=9, fontweight="bold")

plt.show()
Difficulte 1 : nonce =       44, temps = 0.0001 s
Difficulte 2 : nonce =       51, temps = 0.0001 s
Difficulte 3 : nonce =     2194, temps = 0.0027 s
Difficulte 4 : nonce =    93971, temps = 0.1242 s
Difficulte 5 : nonce =   121671, temps = 0.1779 s
_images/54a77ef0b5d90f83c6c4eb0120a7a6df914bd482e8edfab32b70b761357693b5.png

Remarque 19

La preuve de travail est extrêmement coûteuse en énergie. Le réseau Bitcoin consomme environ 150 TWh par an (estimation 2024), soit davantage que la consommation électrique de nombreux pays. Chaque transaction Bitcoin a une empreinte carbone équivalente à des centaines de milliers de transactions par carte bancaire. Cette consommation est inhérente au mécanisme : la sécurité repose sur le fait que la puissance de calcul honnête dépasse celle des attaquants, ce qui crée une course aux armements énergétique.

Remarque 20

L”attaque à 51 % est la menace théorique principale contre un système PoW. Si un attaquant contrôle plus de la moitié de la puissance de calcul du réseau, il peut :

  • miner plus vite que le reste du réseau et construire une chaine alternative plus longue ;

  • réorganiser la chaine en remplacant des blocs déjà confirmés ;

  • annuler des transactions (double-dépense).

En pratique, une telle attaque est extrêmement coûteuse sur les grands réseaux (Bitcoin, Ethereum pre-merge) car elle nécessite d’investir massivement en matériel et en électricité. Sur les petits réseaux, en revanche, des attaques à 51 % ont été réalisées avec succès (Ethereum Classic en 2020, Bitcoin Gold en 2018).

Proof of Stake (PoS)#

La preuve d’enjeu est une alternative à la preuve de travail, concue pour éliminer le gaspillage énergétique tout en maintenant la sécurité du consensus.

Définition 25 (Preuve d’enjeu (Proof of Stake))

La preuve d’enjeu est un mécanisme de consensus dans lequel les validateurs immobilisent (stake) une quantite de jetons natifs de la blockchain comme garantie. La probabilité d’être selectionné pour proposer le prochain bloc est proportionnelle au montant mis en jeu :

\[P(\text{validateur } i \text{ selectionne}) = \frac{s_i}{\sum_{j=1}^{n} s_j}\]

\(s_i\) est le stake du validateur \(i\) et \(n\) le nombre total de validateurs. Le validateur selectionné propose un bloc, et les autres validateurs votent pour l’accepter ou le rejeter.

Définition 26 (Slashing (penalité))

Le slashing est un mécanisme punitif propre aux systèmes PoS. Si un validateur se comporte de manière malveillante (par exemple en signant deux blocs différents à la même hauteur, ou en étant hors ligne pendant trop longtemps), une partie ou la totalité de son stake est confisquée (slashed). Le slashing crée une incitation économique forte a se comporter honnêtement : le validateur a « quelque chose à perdre ».

Exemple 11

Considérons 4 validateurs avec les stakes suivants :

Validateur

Stake (SOL)

Probabilite de selection

A

10 000

10 000 / 40 000 = 25 %

B

15 000

15 000 / 40 000 = 37.5 %

C

5 000

5 000 / 40 000 = 12.5 %

D

10 000

10 000 / 40 000 = 25 %

Le validateur B, avec le plus gros stake, a la plus grande probabilité d’être choisi pour proposer le prochain bloc. S’il triche, il risque de perdre tout ou partie de ses 15 000 SOL.

Comparaison PoW vs PoS#

Critère

Proof of Work (PoW)

Proof of Stake (PoS)

Ressource consommée

Puissance de calcul (electricité)

Capital immobilisé (jetons)

Consommation energétique

Très élevée (~150 TWh/an pour Bitcoin)

Négligeable (réduction > 99 %)

Modèle de sécurité

Attaque à 51 % de la puissance de calcul

Attaque à 33 % du stake total

Risque de centralisation

Economies d’échelle (fermes de minage)

Concentration du capital

Finalite

Probabiliste (plus de confirmations = plus sûr)

Souvent déterministe apres vote

Exemples

Bitcoin, Litecoin

Ethereum (post-merge), Solana, Cardano

Remarque 21

Le problème du nothing-at-stake est une vulnérabilité théorique propre au PoS. Lorsqu’un fork se produit, un validateur rationnel a intérêt à voter sur toutes les branches simultanément, car valider un bloc ne coûte presque rien (contrairement au PoW, où miner sur deux branches divise la puissance de calcul). Si tous les validateurs adoptent cette stratégie, le consensus ne converge jamais.

Les solutions à ce problème incluent :

  • Le slashing : pénaliser les validateurs qui signent sur plusieurs branches ;

  • Les conditions de finalité : une fois un bloc finalisé (apres un nombre suffisant de votes), il ne peut plus être annulé ;

  • Les mecanismes de sélection : comme le leader schedule de Solana, qui designe a l’avance le validateur responsable de chaque creneaux (slot).

Autres mecanismes de consensus#

La PoW et la PoS sont les mécanismes les plus répandus, mais plusieurs variantes existent pour répondre à des contraintes spécifiques de performance, de décentralisation ou de gouvernance.

Définition 27 (Delegated Proof of Stake (DPoS))

Dans la preuve d’enjeu déléguée (DPoS), les détenteurs de jetons votent pour élire un nombre restreint de délègués (généralement entre 21 et 100) qui assurent la production de blocs. Les délégués alternent selon un calendrier prédéterminé. Ce mécanisme offre un débit élevé mais reduit la décentralisation. Exemples : EOS, Tron.

Définition 28 (Practical Byzantine Fault Tolerance (PBFT))

Le PBFT est un algorithme de consensus déterministe pour les systèmes avec un nombre connu de participants. Il fonctionne en trois phases (pre-prepare, prepare, commit) et garantit la finalité immédiate après \(2f + 1\) votes sur \(n = 3f + 1\) noeuds. Sa complexité en messages est \(O(n^2)\), ce qui le rend impraticable pour les réseaux de grande taille. Exemples : Hyperledger Fabric (variante).

Définition 29 (Proof of Authority (PoA))

La preuve d’autorité est un mecanisme dans lequel un ensemble restreint de validateurs autorisés (identifiés par leur identite réelle ou leur reputation) produit les blocs à tour de role. La sécurité repose sur la réputation des validateurs plutôt que sur des incitations économiques. Ce mécanisme est rapide et économe, mais centralisé par conception. Il est utilisé dans les blockchains privées et les testnets.

Définition 30 (Proof of History (PoH))

La preuve d’histoire (Proof of History) est une horloge cryptographique qui etablit un ordre verifiable des évènements avant même que le consensus ne soit atteint. Elle repose sur une séquence de hachages SHA-256 itératifs :

\[h_0 \xrightarrow{\text{SHA-256}} h_1 \xrightarrow{\text{SHA-256}} h_2 \xrightarrow{\text{SHA-256}} \cdots\]

Chaque hash \(h_{i+1} = \text{SHA-256}(h_i)\) ne peut être calculé qu’après le précédent (le hachage est séquentiel et non parallélisable). Le nombre d’itérations entre deux évènements constitue une preuve du temps écoulé. Les transactions sont insérées dans la séquence en étant hachées avec l’etat courant, ce qui leur attribue un horodatage cryptographique.

Remarque 22

Solana combine la Proof of History avec Tower BFT, une variante optimisée du PBFT qui exploite l’horloge PoH. Puisque tous les validateurs disposent d’une source de temps commune et vérifiable, le protocole Tower BFT réduit drastiquement les messages de coordination nécessaires au consensus. C’est cette combinaison qui permet à Solana d’atteindre un débit théorique de 65 000 transactions par seconde avec des temps de finalité de l’ordre de 400 ms. Le chapitre suivant détaille cette architecture.

Forks et réorganisation#

Dans un reseau distribué, il arrive que deux blocs valides soient produits simultanément par des noeuds différents. La chaine se divise alors temporairement en deux branches : c’est un fork.

Définition 31 (Fork (embranchement))

Un fork est une divergence dans la blockchain, où deux blocs différents partagent le même bloc parent. Il existe deux types de forks :

  1. Fork accidentel : deux mineurs/validateurs produisent un bloc valide au même instant. Le réseau se divise temporairement, puis converge vers une branche unique grace à la règle de la chaine la plus longue (PoW) ou à un mecanisme de finalité (PoS).

  2. Fork intentionnel : une modification delibérée du protocole. Il se subdivise en :

    • Soft fork : changement rétrocompatible (les anciens noeuds acceptent les nouveaux blocs) ;

    • Hard fork : changement non retrocompatible (les anciens noeuds rejettent les nouveaux blocs, creant deux chaines distinctes).

Exemple 12

Parmi les hard forks les plus célèbres :

  • Ethereum / Ethereum Classic (2016) : après le piratage de The DAO, la communauté Ethereum a voté un hard fork pour annuler le vol. Les opposants ont maintenu la chaine originale sous le nom d’Ethereum Classic.

  • Bitcoin / Bitcoin Cash (2017) : un désaccord sur la taille des blocs (1 Mo vs 8 Mo) a conduit a la création de Bitcoin Cash.

Visualisons un fork accidentel et sa résolution par la règle de la chaine la plus longue.

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import networkx as nx
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

G = nx.DiGraph()

# Chaine commune
G.add_edge("B0", "B1")
G.add_edge("B1", "B2")
G.add_edge("B2", "B3")

# Branche principale (gagnante)
G.add_edge("B3", "B4a")
G.add_edge("B4a", "B5a")
G.add_edge("B5a", "B6a")

# Branche orpheline
G.add_edge("B3", "B4b")
G.add_edge("B4b", "B5b")

pos = {
    "B0": (0, 0),
    "B1": (1.5, 0),
    "B2": (3, 0),
    "B3": (4.5, 0),
    "B4a": (6, 0.8),
    "B5a": (7.5, 0.8),
    "B6a": (9, 0.8),
    "B4b": (6, -0.8),
    "B5b": (7.5, -0.8),
}

# Couleurs par type
c_common = "#4C72B0"
c_main = "#55A868"
c_orphan = "#C44E52"

colors_map = {
    "B0": c_common, "B1": c_common, "B2": c_common, "B3": c_common,
    "B4a": c_main, "B5a": c_main, "B6a": c_main,
    "B4b": c_orphan, "B5b": c_orphan,
}
node_colors = [colors_map[n] for n in G.nodes()]

fig, ax = plt.subplots(figsize=(12, 5))

nx.draw_networkx_nodes(G, pos, ax=ax, node_size=1000, node_color=node_colors,
                       edgecolors="white", linewidths=2)
nx.draw_networkx_labels(G, pos, ax=ax, font_size=11, font_weight="bold", font_color="white")
nx.draw_networkx_edges(G, pos, ax=ax, edge_color="#888888", arrows=True,
                       arrowstyle="-|>", arrowsize=15, connectionstyle="arc3,rad=0.05",
                       min_source_margin=18, min_target_margin=18)

# Annotations
ax.annotate("Fork", xy=(4.5, 0), xytext=(4.5, -1.8),
            fontsize=11, fontweight="bold", ha="center", color="#333333",
            arrowprops=dict(arrowstyle="->", color="#333333", lw=1.5))

# Legende
legend_elements = [
    mpatches.Patch(facecolor=c_common, label="Chaine commune"),
    mpatches.Patch(facecolor=c_main, label="Branche gagnante (la plus longue)"),
    mpatches.Patch(facecolor=c_orphan, label="Branche orpheline (abandonnée)"),
]
ax.legend(handles=legend_elements, loc="upper left", fontsize=9, frameon=True)

ax.set_title("Fork et résolution par la règle de la chaine la plus longue",
             fontsize=13, fontweight="bold", pad=15)
ax.axis("off")

plt.show()
_images/341206e551c75b493b4b51afabe52ae08f601ca62b808047330497243a122193.png

Remarque 23

La distinction entre soft fork et hard fork est essentielle pour la gouvernance d’une blockchain :

  • Un soft fork resserre les règles : les blocs valides selon les nouvelles règles sont également valides selon les anciennes. Les noeuds non mis à jour continuent de fonctionner (même s’ils ne bénéficient pas des nouvelles fonctionnalités). Exemple : l’activation de SegWit sur Bitcoin en 2017.

  • Un hard fork élargit ou modifie les règles de manière incompatible : les blocs valides selon les nouvelles règles sont rejetés par les anciens noeuds. Si tous les participants ne migrent pas, la blockchain se scinde définitivement en deux réseaux distincts.

En general, les soft forks sont préférés car ils ne fragmentent pas la communauté. Les hard forks sont reservés aux changements profonds du protocole.

Résumé#

Le tableau suivant recapitule les concepts clés de ce chapitre.

Concept

Définition

Point clé

Bloc

Unite de stockage : en-tête (hash précédent, racine de Merkle, nonce, timestamp) + corps (transactions)

La racine de Merkle résume toutes les transactions en un seul hash

Blockchain

Liste chainée de blocs liés par hashes cryptographiques

Modifier un bloc invalide tous les blocs suivants (immutabilité)

Généraux byzantins

Problème de consensus en présence de noeuds malveillants

Nécessite \(n > 3f\) noeuds pour tolérer \(f\) traitres

Proof of Work

Trouver un nonce tel que le hash du bloc soit inférieur à une cible

Sécurisé mais extrêmement énergivore

Proof of Stake

Validateurs sélectionnés proportionnellement à leur mise en jeu

Econome en énergie, sécurisé par le slashing

DPoS

Election de délégués par vote des détenteurs de jetons

Débit élevé, décentralisation réduite

PBFT

Consensus déterministe en \(O(n^2)\) messages

Finalité immédiate, ne passe pas à l’échelle

Proof of History

Horloge cryptographique par hachage séquentiel

Etablit l’ordre des évènements avant le consensus

Fork

Divergence de la chaine en deux branches

Résolu par la chaine la plus longue (PoW) ou la finalité (PoS)