forked from yvv/decision
v3 : ADR 0008, le vote par transaction sur la chaîne Ğ1 ; chantier, contrôles C21–C24, structure
- ADR 0008 : bulletin scellé (ld1: + blake2b-256, sel de 128 bits), la transaction utility.batchAll (virement + remarque), la confirmation sur l'indexeur, le dernier fait foi dans l'ordre de la chaîne, groupes gérés, ce que voit la chaîne, ce que sait le serveur, réglages LD_VOTE_*, coût et limites. Noté dans 0006. - CHANTIER : le vote déplié, le panier, « une personne, une voix » par la chaîne ; le droit de vote se vérifie au moment de la confirmation sur la chaîne. - CONTROLES : C4, C5, C6, C11, C17, C19, C20, M1 mis à jour ; C21–C24 ; passage du lot B, 25 / 25 verts. - CLAUDE.md : ballots.rs, indexer.rs, ballot/address du moteur, wallet/, panier, chaîne simulée des e2e. Compose : où poser LD_VOTE_ACCOUNT (en commentaire). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,9 @@ Duniter peut se loger et tout faire — sauf la gestion des accès », qui revie
|
||||
- **Une personne, une voix** : là où l'électorat est la toile de confiance (Monnaie libre),
|
||||
seuls ses membres votent ; une identité d'invitation y lit et propose. Un nouveau lien ne
|
||||
vise jamais une identité Ğ1 (un administrateur ouvrirait une session à sa place).
|
||||
*Depuis le 2026-10-30 ([0008](0008-vote-par-transaction.md)) : dans Monnaie libre, chaque vote
|
||||
est une transaction de la chaîne Ğ1 ; la voix est celle de l'identité du compte qui signe,
|
||||
quelle que soit la session.*
|
||||
- Premier administrateur : un lien d'invitation écrit au premier démarrage dans le volume de
|
||||
données (0600), jamais dans les journaux.
|
||||
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
# 0008 — Le vote par transaction sur la chaîne Ğ1
|
||||
|
||||
> Complète [0002](0002-vote-permanent.md) (vote permanent) et [0006](0006-acces-connect-trustinwallet.md)
|
||||
> (accès). Décision d'Yvv du 2026-10-30 (revue, L9) ; le format du bulletin, la voie du
|
||||
> portefeuille et les réglages sont les choix par défaut d'orch (Q5, Q6, Q7), isolés pour être
|
||||
> changés sans toucher au reste.
|
||||
|
||||
**Contexte.** Jusqu'ici, un vote était une ligne écrite par le serveur pour la session connectée.
|
||||
Dans Monnaie libre, l'électorat W est la toile de confiance, mais la connexion Ğ1
|
||||
(connect.monnaie-libre.fr) n'est pas active et toutes les identités viennent d'invitations : en
|
||||
pratique, personne ne pouvait voter. Yvv, 2026-10-30 : « une personne une voix, mais la voix peut
|
||||
s'exprimer plusieurs fois […] c'est le dernier qui fait foi. Pour garantir l'intégrité d'un vote,
|
||||
je crois qu'il n'y a que la blockchain, donc une transaction par vote » ; « 0,1 Ğ1 » ; « qu'un
|
||||
geste dans le vote » ; « Ğ1Companion ou Ğecko, à terme trustinWallet » ; « Je peux voter tout
|
||||
d'un coup, ou une section entière, ou un article avec son titre, ou séparément. »
|
||||
|
||||
## Décision
|
||||
|
||||
### Qui vote où
|
||||
|
||||
- **Où l'électorat est la toile** (politique `g1-members` : Monnaie libre), chaque vote est **une
|
||||
transaction de la chaîne Ğ1**, signée par le portefeuille de la personne. Le serveur n'y écrit
|
||||
plus de vote direct (`409 chain_only`). La voix est celle de **l'identité Ğ1 du compte qui
|
||||
signe**, quelle que soit la session qui a préparé le vote (invitation, plus tard connexion Ğ1).
|
||||
- **Dans un groupe géré** (essai, Axiom…), le vote reste écrit par le serveur, sans chaîne ni
|
||||
portefeuille, comme avant (`409 server_vote` si on y demande un bulletin). Le geste groupé y
|
||||
marche aussi : une seule écriture.
|
||||
- La règle est dans trustinWallet : `trustin_access::vote_way` (`chain` / `server`),
|
||||
`votes_in`, `asks_ballot`, `chain_vote_counts`.
|
||||
|
||||
### Le bulletin scellé (Q5) — `crates/ld-engine/src/ballot.rs`
|
||||
|
||||
Le serveur scelle un **bulletin en attente** : le document, le compte qui signera (quand la page
|
||||
le connaît), les lignes (version, révision votée, choix), un sel de 128 bits tiré au hasard. Sa
|
||||
forme canonique, en ASCII, les lignes triées par version :
|
||||
|
||||
```
|
||||
ld1
|
||||
doc <id du document>
|
||||
author <adresse Ğ1 du compte qui signe, ou ->
|
||||
line <id de la version> <révision> <for|against|none>
|
||||
…
|
||||
salt <32 chiffres hexadécimaux>
|
||||
```
|
||||
|
||||
`H = blake2b-256(forme canonique)` ; la **remarque** de la transaction est `ld1:` suivi des 64
|
||||
chiffres hexadécimaux de H, 68 octets ASCII. Exemple (vecteur des tests, calculé à part) :
|
||||
|
||||
```
|
||||
ld1
|
||||
doc d0c
|
||||
author -
|
||||
line p1 3 for
|
||||
line p2 1 against
|
||||
salt 00112233445566778899aabbccddeeff
|
||||
```
|
||||
|
||||
→ remarque `ld1:20448d496744292981c8586856afb58a86e37da2470f764b4e6ed84eade71340`, que
|
||||
l'indexeur retrouve par `blake2b-256(remarque)` =
|
||||
`0xe765221a75716dde863a7b53df3a97acf6db38a2118edd525d54c8a2979b7bc3`.
|
||||
|
||||
Le sel empêche de deviner le choix (trois choix par ligne se devineraient tous). L'adresse liée
|
||||
empêche qu'un autre compte, qui recopierait la remarque, prenne le vote (voir les limites).
|
||||
|
||||
### La transaction (Q6) — `web/src/lib/wallet/`
|
||||
|
||||
```
|
||||
utility.batchAll([
|
||||
balances.transferKeepAlive(LD_VOTE_ACCOUNT, LD_VOTE_AMOUNT),
|
||||
system.remarkWithEvent("ld1:" + H),
|
||||
])
|
||||
```
|
||||
|
||||
Un seul geste, atomique : le virement et la remarque passent ensemble ou pas du tout. La page la
|
||||
fait signer par l'extension de portefeuille du navigateur (interface standard `injectedWeb3` /
|
||||
`signPayload` : duniter-connect aujourd'hui, Ğ1Companion ou trustinWallet le jour où elles
|
||||
l'offrent), avec **polkadot-api chargé au seul moment de signer**, et l'envoie à un nœud Ğ1
|
||||
(`LD_VOTE_NODES`). Sans portefeuille dans le navigateur : **Ğecko**, par un lien et un QR code
|
||||
`g1://<compte>?amount=0.1&comment=ld1:<H>` (un virement commenté, ce que Ğecko sait faire) ;
|
||||
ce bulletin-là n'est lié à aucun compte, et la page attend.
|
||||
|
||||
### La confirmation — `crates/ld-server/src/ballots.rs`, `indexer.rs`
|
||||
|
||||
Le serveur lit l'indexeur (duniter-squid, `LD_INDEXERS`) :
|
||||
|
||||
```graphql
|
||||
query Ballot($hash: String!) {
|
||||
txComments(condition: {hash: $hash}, orderBy: BLOCK_NUMBER_ASC, first: 20) {
|
||||
nodes { id blockNumber remark
|
||||
author { id identity { index name isMember } }
|
||||
transfersByCommentId { nodes { fromId toId amount } } } }
|
||||
}
|
||||
```
|
||||
|
||||
`hash` = `0x` + blake2b-256 des octets de la remarque. Il garde la première occurrence, dans
|
||||
l'ordre de la chaîne, qui ne vient pas d'un autre compte que celui lié, puis exige :
|
||||
|
||||
1. la remarque exacte ;
|
||||
2. dans la même transaction, un virement **du signataire vers le compte des votes**, d'au moins
|
||||
le montant ;
|
||||
3. une identité Ğ1 au signataire (`no_identity` sinon) ;
|
||||
4. que cette identité soit **membre maintenant** (`not_member` sinon) — par la règle de
|
||||
trustinWallet.
|
||||
|
||||
Le vote compte alors pour `idty:<index>` (créée si besoin : sorte g1, membre), ligne par ligne,
|
||||
avec le bloc et le rang de l'évènement ; le balayage d'adoption tourne aussitôt. La session qui a
|
||||
demandé le bulletin voit ces votes comme « ton vote » — et seulement ceux de ses bulletins : une
|
||||
autre session ne voit jamais ce qu'a voté la même identité ailleurs.
|
||||
|
||||
Le serveur relit l'indexeur pour chaque bulletin en attente toutes les 10 secondes (tâche de
|
||||
fond) et quand la page demande (au plus une lecture toutes les 3 secondes par bulletin). Un
|
||||
bulletin non vu en **24 heures** est lâché. Cas d'erreur, chacun testé : indexeur muet (le
|
||||
bulletin reste en attente), signataire non membre ou sans identité (refusé, et dit), mauvais
|
||||
montant ou mauvais destinataire (refusé), déjà confirmé (rien ne change), révision changée entre
|
||||
temps (le vote porte sur la révision votée : « à reconfirmer »).
|
||||
|
||||
### Le dernier fait foi
|
||||
|
||||
Par version et par identité, **dans l'ordre de la chaîne** : le bloc, puis le rang de
|
||||
l'évènement (lu dans l'identifiant de l'évènement). Un bulletin confirmé tard ne remplace jamais
|
||||
un bulletin plus récent sur la chaîne. « Retirer mon vote » est un bulletin dont la ligne vaut
|
||||
`none`. Les transactions d'un même compte gardent l'ordre de ses envois (le nonce) ; le désordre
|
||||
ne vient que de l'ordre de lecture de l'indexeur, et la règle l'absorbe.
|
||||
|
||||
Un bulletin que le portefeuille n'a pas signé (refus, fonds insuffisants, compte absent) est lâché
|
||||
aussitôt par la page (`DELETE /api/ballots/{id}` : `expired`, raison `dropped`), pour ne pas
|
||||
rester « en attente » une journée. Jamais un bulletin confirmé.
|
||||
|
||||
### Le vote groupé
|
||||
|
||||
Un panier : article par article ; « tout pour » / « tout contre » pour une section ou tout le
|
||||
document — jamais pour un article dont plusieurs versions sont en concurrence : l'interface le
|
||||
montre, on choisit sa version. Puis **un geste** : une écriture (groupe géré), ou **une
|
||||
transaction** (chaîne) pour toutes les lignes, au même prix qu'une seule (0,1 Ğ1 par transaction,
|
||||
pas par ligne ; 200 lignes au plus, 20 bulletins en attente par session).
|
||||
|
||||
### Ce que voit la chaîne
|
||||
|
||||
Qu'un compte (et son identité) a envoyé 0,1 Ğ1 au compte des votes avec une remarque `ld1:…`, à
|
||||
tel bloc : **qui a voté et quand, jamais quoi** — ni le document, ni le nombre de lignes, ni les
|
||||
choix. Le solde du compte des votes est public : il dit combien de votes ont été envoyés.
|
||||
|
||||
### Ce que sait le serveur
|
||||
|
||||
Les bulletins, leurs lignes, leurs sels, la session qui les a demandés et l'identité qui les a
|
||||
signés : il sait qui a voté quoi, comme pour un vote direct, et ne montre que les totaux. Il ne
|
||||
peut pas inventer un vote d'une identité (il faudrait sa signature sur la chaîne) ; il pourrait en
|
||||
taire un. Chaque vote compté se vérifie contre la chaîne en recalculant H depuis les lignes et le
|
||||
sel — ce que personne d'autre ne peut faire tant que les sels restent au serveur.
|
||||
|
||||
### Les réglages (Q7) — `crates/ld-server/src/config.rs`
|
||||
|
||||
- `LD_VOTE_ACCOUNT` : le compte des votes, une adresse Ğ1 (toute forme SS58, ramenée au préfixe
|
||||
g1). **Sans lui, le vote par la chaîne est fermé** et l'interface le dit avec des mots simples ;
|
||||
les groupes gérés votent comme avant. Une adresse illisible est journalisée et laisse le vote
|
||||
fermé. Aucune adresse n'est écrite en dur.
|
||||
- `LD_VOTE_AMOUNT` : en centimes de Ğ1, 10 par défaut (0,1 Ğ1), au moins 1.
|
||||
- `LD_VOTE_NODES` : les nœuds où la page envoie la transaction (par défaut
|
||||
`wss://rpc.duniter.org`, `wss://g1.axiom-team.fr/ws`).
|
||||
- `LD_VOTE_TEST_NODE` : tests seulement, et seulement avec la connexion de développement : la page
|
||||
envoie ce que le faux portefeuille a signé à un faux nœud, sans polkadot-api.
|
||||
|
||||
## Les données
|
||||
|
||||
Migration `0006`, additive : `ballots` (le bulletin, son sel, H, `0x`+blake2b-256 de la remarque,
|
||||
le compte et le montant du moment, l'état `pending | confirmed | refused | expired`, la raison,
|
||||
l'identité qui a voté, le signataire, le bloc, le rang, l'évènement, ce qui est entré en vigueur,
|
||||
les dates) et `ballot_lines` (version, révision, choix). `votes` gagne `source` (`server` par
|
||||
défaut : tous les votes d'avant restent tels quels), `block`, `event_index`, `ballot_id`.
|
||||
|
||||
## Coût
|
||||
|
||||
- Voter coûte 0,1 Ğ1 par geste, plus les frais de la chaîne (non mesurés ici) ; le portefeuille
|
||||
doit garder au moins 1 Ğ1 (dépôt existentiel, `transferKeepAlive`). Le compte des votes
|
||||
accumule ces Ğ1 : à qui il appartient, et ce qu'on en fait, reste à décider par Yvv.
|
||||
- Le serveur **fait confiance à l'indexeur** pour la remarque, le virement, l'identité et
|
||||
l'appartenance à la toile : un indexeur menteur ferait compter ou refuser un vote. Plusieurs
|
||||
indexeurs se configurent ; lire les évènements sur un nœud serait l'étape suivante.
|
||||
- Le droit de vote se vérifie **au moment de la confirmation sur la chaîne** : un membre sorti
|
||||
de la toile ensuite garde ses voix.
|
||||
- Par Ğecko, le bulletin n'est lié à aucun compte : un autre compte qui recopierait la remarque
|
||||
et la ferait inclure avant (en payant 0,1 Ğ1) prendrait ce bulletin — ses lignes compteraient
|
||||
pour lui. Par l'extension, le bulletin est lié au compte et la copie est ignorée.
|
||||
- Un paiement Ğecko fait après 24 heures ne compte pas (il est perdu pour le vote).
|
||||
- polkadot-api : environ 180 Ko (66 Ko compressés) chargés au moment de signer seulement ;
|
||||
`node_modules` grossit d'environ 77 Mo (son outillage).
|
||||
- La chaîne montre la participation de chacun (qui a voté, quand, combien de fois).
|
||||
@@ -12,3 +12,4 @@ pas une décision : on en écrit une nouvelle qui la remplace, et on le note dan
|
||||
| [0005](0005-publication-git.md) | Publication par file d'envoi vers un dépôt git | 2026-10-21 |
|
||||
| [0006](0006-acces-connect-trustinwallet.md) | Accès : connect.monnaie-libre.fr, invitations, trustinWallet | 2026-10-21 |
|
||||
| [0007](0007-inertie-cinq-crans.md) | Inertie à cinq crans, calée sur la participation du groupe (complète 0002) | 2026-10-30 |
|
||||
| [0008](0008-vote-par-transaction.md) | Le vote par transaction sur la chaîne Ğ1 : bulletin scellé, confirmation par l'indexeur, le dernier fait foi | 2026-10-30 |
|
||||
|
||||
Reference in New Issue
Block a user