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>
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 :
- Authentification -- Verification de l'identite des membres via signature Ed25519
- Donnees WoT -- Recuperation des tailles WoT, Smith et TechComm pour le calcul des seuils
- 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 :
- Recalculer le hash SHA-256 du contenu
- Rechercher le remark correspondant dans la blockchain
- 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 :
- Le serveur genere un challenge aleatoire (64 caracteres hexadecimaux)
- Le client signe le challenge avec sa cle privee Ed25519 (Duniter V2)
- 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.