Implémentation création manuelle du DU et guide pour dev clients/wallet

La création manuelle du DU a été implémentée, et sera disponible dès le genesis de la nouvelle ĞDev après le 1er reset (vers début septembre).

En attendant, cette fonctionnalité est déjà disponible sur l’image duniter/duniter-v2s:debug-latest, vous pouvez donc déjà la tester avec une blockchain locale :slight_smile:

La Merge Request : feat(runtime): create UDs manually with new call universalDividend.claim_uds (!83) · Merge requests · nodes / rust / Duniter v2S · GitLab

Résumé de l’implémentation

Pour simplifier et optimiser l’implémentation, je « compte » le nombre de DU créés en incrémentant un nombre entier à chaque nouveau DU, c’est le currentUdIndex. Cela permet également de découpler totalement l’implémentation de toute notion de temps, elle restera donc fonctionnelle même si on fait varier le temps entre deux DU ou entre deux réévaluations.

Chaque identité a un nouveau champ data: ItdyData, qui permet de stocker des données supplémentaires, pas utilisées par la pallet identity, mais nécessaire pour d’autres pallets.
Pour le moment, le type ItdyData contient un seul champ firstEligibleUd, qui correspond à l’index du premier DU réclamable, mais on pourra ajouter d’autres champs à l’avenir si besoin.

Du coup, pour savoir combien de DU il faut créer lorsqu’un utilisateur réclame ses DU, il suffit de faire la soustraction currentUdIndex - firstEligibleUd.
Mais en réalité, on veut connaître ce nombre pour chaque montant du DU (entre chaque réévaluation).
Pour ce faire, le storage de la pallet universal dividend contient un nouvel item pastReevals, qui contient un tableau de tuple (UdIndex, Balance).

L’algo qui calcule le montant total à verser lors d’une réclamation des DU est ici : pallets/universal-dividend/src/compute_claim_uds.rs · master · nodes / rust / Duniter v2S · GitLab

Guide pour les développeurs des Clients/Wallets

Définitions

Solde réel : Le solde réel est la somme free + reserved de l’objet system.account(address).data.

Solde potentiel : L’addition du solde réel et de la somme des DU non-réclamés.

Solde transférable : La quantité maximale de monnaie qui peut être envoyée sur un autre compte sans prise en compte des frais, le solde transférable vaut donc : « solde potentiel - solde réservé ».

Recommandations

Je vous recommande de faire en sorte que la notion de création manuelle du DU soit totalement masquée à l’utilisateur, car ce n’est en place que pour des raisons d’optimisations, il n’y a aucune nécessité ni aucun intérêt d’embêter l’utilisateur avec ça.

L’idée est d’afficher à l’utilisateur son solde potentiel, et de réclamer automatiquement ses DU lors d’un virement s’ils sont nécessaires pour que le virement réussisse ou si les DU n’ont pas été réclamés depuis longtemps.

Cela fonctionne, car les batchs sont séquentiels, il suffit donc de remplacer le call

balance.transferKeepAlive(dest, amount)

par

utility.batchAll([universalDividend.claimUds(), balance.transferKeepAlive(dest, amount)])

Le coût d’exécution de universalDividend.claimUds() est faible quel que soit le nombre de DUs à verser, donc l’utilisateur attentif verra juste que la transaction semble consommer un peu plus de frais/quotas que d’habitude.

Exemple

Alice à 100 Ğ1 (free balance), 20 Ğ1 bloquées dans un proxy (reserved), et 3 DU non-réclamés, le DU courant vaut 10 Ğ1.

Le solde potentiel d’Alice est 150 Ğ1, son solde transférable est 130 Ğ1.

Si Alice veut envoyer 110 Ğ1 à Bob, elle peut le faire directement en un seul extrinsic contenant le call :

utility.batchAll([universalDividend.claimUds(), balance.transferKeepAlive(Bob, 11000)])

La blockchain va alors prélever les frais (disons 1 Ğ1), puis exécuter claimUds, ce qui va incrémenter la free balance d’Alice de 30 Ğ1, puis exécuter transferKeepAlive, ce qui va prendre 110 Ğ1 de la free balance d’Alice pour les ajouter à la free balance de Bob.

Après la transaction, le nouveau solde potentiel d’Alice est 39 Ğ1, dont 19 transférables.

Afficher le solde potentiel

Je vous recommande d’afficher à l’utilisateur le solde potentiel, et seulement si l’utilisateur souhaite le détail (par un clic sur le solde ou une petite flèche à côté), afficher à côté ou en dessous le solde transférable.

Pour calculer le solde potentiel d’un compte, vous avez trois possibilités :

  1. Le calculer côté client à partir des données du storage, je détaille plus bas comment faire, c’est la méthode recommandée si vous n’êtes pas dans un environnement web.
  2. Demander directement le solde potentiel d’un compte à l’extension g1/g1-connect/G1-compagnon, on à pas encore entériné le nom, mais avec @ManUtopiK et @1000i100 on va coder une extension web pour duniter-v2s, qui gérera les comptes Ğ1 (comme l’extension polkadotjs) et qui proposera également d’autres fonctionnalités à terme, dont la possibilité de demander directement à l’extension certaines informations sur un compte, dont son solde potentiel.
  3. Passer par un indexer qui le calculera pour vous. Je déconseille cette solution, car ça demande de truster l’indexeur sur une information critique qu’est le solde.

Calculer le solde potentiel

Pour ce faire, il vous faut requêter cinq éléments de storage :

  1. system.account(address)
  2. identity.identityIndexOf(address)
  3. identity.identities(idtyIndex)
  4. universalDividend.currentUdIndex()
  5. universalDividend.pastReevals()

Par exemple en javascript ça donnerait quelque chose comme ça :

const api = await ApiPromise.create(...);
const { data: balance } = await api.query.system.account(address);
const idtyIndex = await api.query.identity.identityIndexOf(address);
const { data: idtyData } = await api.query.identity.identies(idtyIndex);
const currentUdIndex = await api.query.universalDividend.currentUdIndex();
const pastReevals = await api.query.universalDividend.pastReevals();

J’ai pas testé ce code, le but est juste pour vous guider par l’exemple, pas de coder l’implémentation à votre place :stuck_out_tongue:

À partir de ces données, vous pouvez alors calculer le solde des DU non-réclamés, voici l’algo utilisé en Rust (code simplifié) :

fn compute_claim_uds(
    mut current_ud_index: UdIndex,
    first_ud_index: UdIndex,
    past_reevals: impl DoubleEndedIterator<Item = (UdIndex, Balance)>,
) -> Balance {
    let mut total_amount = Zero::zero();

    // Iterate reevaluations in reverse order (recent -> old)
    for (ud_index, ud_amount) in past_reevals.rev() {
        if ud_index <= first_ud_index {
            let count = current_ud_index - first_ud_index;
            total_amount += Balance::from(count) * ud_amount;
            break;
        } else {
            let count = current_ud_index - ud_index;
            total_amount += Balance::from(count) * ud_amount;
            current_ud_index = ud_index;
        }
    }

    total_amount
}

Je laisse les fans d’algorithmique traduire cet algo en js/python/dart, il vous suffit de lire le résumé de l’implémentation au début de ce post pour comprendre cet algo :slight_smile:

Enfin, il suffit de sommer le tout :

let newUdsAmount = computeClaimUds(currentUdIndex, idtyData.firstEligibleUd, pastReevals);
let transferableBalance = balance.free + newUdsAmount;
let potentialBalance = balance.reserved + transferableBalance;
7 Likes

Hello, j’ai fait une revue du code, rapidement grâce à tes explications, merci pour ça.

Je craignais que tu n’aies pas géré la perte de statut de membre (qui aurait fichu en l’air cette idée de firstEligibleUd), mais j’ai repéré la fonction on_removed_member qui s’occupe de produire les DUs automatiquement lors de cet événement.

Du coup c’est tout bon, l’algo respecte bien la production du DU de façon optimisée pour la blockchain, c’est top :slight_smile:

Juste un point :

Changes merged into master with 52ebb5ef (commits were squashed).

Serait-il possible de changer le comportement par défaut des MR afin de ne pas squasher et conserver un commit de merge ? Le squash fait perdre beaucoup de précieuses informations.

2 Likes

Cette information se trouve dans l’issue associée, sur laquelle j’avais mis à plat mes réflexions sur la conception en me répondant régulièrement à moi-même.
Tu trouveras d’autres issues, dont certaines encore ouvertes, où je couche par écrit l’état de mes réflexions de la même façon :slight_smile:

Au contraire j’ai volontairement modifié le workflow pour forcer le squash, c’est le seul moyen que j’ai trouvé pour permettre de déplacer coté mainteneur la responsabilité du nommage des commits qui finiront sur la branche principale, car le nom du commit final peut alors être choisi par le mainteneur juste avent de merger, sans avoir besoin de push force.
Sans le squash, j’exclue toute contribution des personnes qui ne maîtrise pas assez bien git, comme @vincentux par exemple, c’est d’ailleurs la 1er MR de vincentux qui m’a définitivement convaincu d’adopter ce nouveau workflow, auquel je réfléchissais depuis déjà quelques mois.

Ce nouveau workflow me conviens beaucoup mieux, je ne compte pas revenir en arrière :slight_smile:

La liste originale des commits reste trackée par gitlab:

Ce sont des commits de travail, qui peuvent être mal faits, et qui ne sont pas censer apporter d’informations utiles pérennes.

S’il te manque des informations, c’est que le code n’est pas assez commenté où/et qu’il n’y a pas assez de documentation. Ajouter de la documentation est une bonne 1ère contribution, j’avais commencé comme ça pour rentrer dans duniter V1 :slight_smile:

2 Likes

2 messages ont été scindés en un nouveau sujet : Git/GitLab : merge request et squash pour duniter-v2s

Je viens d’implémenter l’affichage des soldes transferables incluants les DUs non réclamés dans Ğecko, voici l’implémentation dont j’ai sélectionné les 50 lignes concernés: lib/providers/substrate_sdk.dart · 2526f382f1bbd383f9fd814060018e54f1b56720 · clients / Ğecko · GitLab

Pour le moment je n’ai pu tester que sur mon nœud v2s de dev local, car il me faut de la donnée dans le temps pour vérifier le résultat, ça semble correcte.

Je me suis beaucoup inspiré de ton exemple rust @elois merci ^^

1 Like