Files
decision/docs/content/dev/6.blockchain-integration.md
Yvv 25437f24e3 Sprint 1 : scaffolding complet de Glibredecision
Plateforme de decisions collectives pour Duniter/G1.
Backend FastAPI async + PostgreSQL (14 tables, 8 routers, 6 services,
moteur de vote avec formule d'inertie WoT/Smith/TechComm).
Frontend Nuxt 4 + Nuxt UI v3 + Pinia (9 pages, 5 stores).
Infrastructure Docker + Woodpecker CI + Traefik.
Documentation technique et utilisateur (15 fichiers).
Seed : Licence G1, Engagement Forgeron v2.0.0, 4 protocoles de vote.
30 tests unitaires (formules, mode params, vote nuance) -- tous verts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:46:11 +01:00

4.2 KiB

title, description
title description
Integration blockchain Integration Duniter V2, IPFS et ancrage on-chain

Integration blockchain

Glibredecision s'integre a la blockchain Duniter V2 pour trois fonctions essentielles :

  1. Authentification -- Verification de l'identite des membres via signature Ed25519
  2. Donnees WoT -- Recuperation des tailles WoT, Smith et TechComm pour le calcul des seuils
  3. Ancrage on-chain -- Archivage immuable des resultats via system.remark

Duniter V2 RPC

La communication avec le noeud Duniter V2 utilise la bibliotheque substrate-interface via WebSocket RPC.

Configuration

DUNITER_RPC_URL=wss://gdev.p2p.legal/ws

Requetes principales

Taille de la WoT (membres)

from substrateinterface import SubstrateInterface

substrate = SubstrateInterface(url="wss://gdev.p2p.legal/ws")
result = substrate.query(
    module="Membership",
    storage_function="MembershipCount",
)
wot_size = int(result.value)

Taille Smith (forgerons)

result = substrate.query(
    module="SmithMembers",
    storage_function="SmithMembershipCount",
)
smith_size = int(result.value)

Taille TechComm

result = substrate.query(
    module="TechnicalCommittee",
    storage_function="Members",
)
techcomm_size = len(result.value) if result.value else 0

Cache blockchain

Pour eviter des appels RPC repetes, les donnees blockchain sont mises en cache dans la table blockchain_cache avec une duree d'expiration configurable. La cle de cache est une chaine descriptive (ex: "wot_size", "smith_size"), la valeur est stockee en JSONB.

IPFS (kubo)

Le composant IPFS est un noeud kubo qui sert de stockage distribue pour le Sanctuaire. Chaque document adopte, resultat de vote ou decision finalisee est uploade sur IPFS.

Configuration

IPFS_API_URL=http://localhost:5001
IPFS_GATEWAY_URL=http://localhost:8080

Upload de contenu

import httpx

async with httpx.AsyncClient() as client:
    response = await client.post(
        f"{IPFS_API_URL}/api/v0/add",
        files={"file": ("content.txt", content.encode("utf-8"))},
    )
    response.raise_for_status()
    ipfs_cid = response.json()["Hash"]

Acces au contenu

Le contenu est accessible via la passerelle IPFS :

GET http://localhost:8080/ipfs/{cid}

Ancrage on-chain (system.remark)

L'ancrage on-chain consiste a soumettre un extrinsic system.remark contenant le hash SHA-256 du contenu archive. Cela cree une preuve immuable et horodatee sur la blockchain Duniter V2.

Format du remark

glibredecision:sanctuary:{content_hash_sha256}

Soumission

from substrateinterface import SubstrateInterface, Keypair

substrate = SubstrateInterface(url="wss://gdev.p2p.legal/ws")

call = substrate.compose_call(
    call_module="System",
    call_function="remark",
    call_params={"remark": f"glibredecision:sanctuary:{content_hash}"},
)

extrinsic = substrate.create_signed_extrinsic(call=call, keypair=keypair)
receipt = substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)

tx_hash = receipt.extrinsic_hash
block_number = receipt.block_number

Verification

Pour verifier qu'un contenu a ete ancre, il suffit de :

  1. Recalculer le hash SHA-256 du contenu
  2. Rechercher le remark correspondant dans la blockchain
  3. Verifier que le hash correspond

Flux complet du Sanctuaire

Contenu adopte
    |
    v
[SHA-256] --> content_hash
    |
    +---> [IPFS /api/v0/add] --> ipfs_cid
    |
    +---> [system.remark] --> tx_hash, block_number
    |
    v
[sanctuary_entries] -- Enregistrement en base avec content_hash, ipfs_cid, chain_tx_hash, chain_block

Authentification Ed25519

Le flux d'authentification utilise un mecanisme challenge-response :

  1. Le serveur genere un challenge aleatoire (64 caracteres hexadecimaux)
  2. Le client signe le challenge avec sa cle privee Ed25519 (Duniter V2)
  3. Le serveur verifie la signature a l'aide de la cle publique derivee de l'adresse SS58
from substrateinterface import Keypair

keypair = Keypair(ss58_address=address)
is_valid = keypair.verify(challenge_bytes, signature_bytes)

Cette methode garantit que seul le proprietaire de l'adresse Duniter peut s'authentifier, sans jamais transmettre la cle privee.