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>
This commit is contained in:
165
docs/content/dev/6.blockchain-integration.md
Normal file
165
docs/content/dev/6.blockchain-integration.md
Normal file
@@ -0,0 +1,165 @@
|
||||
---
|
||||
title: Integration blockchain
|
||||
description: 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)
|
||||
|
||||
```python
|
||||
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)
|
||||
|
||||
```python
|
||||
result = substrate.query(
|
||||
module="SmithMembers",
|
||||
storage_function="SmithMembershipCount",
|
||||
)
|
||||
smith_size = int(result.value)
|
||||
```
|
||||
|
||||
#### Taille TechComm
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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.
|
||||
Reference in New Issue
Block a user