Schéma de release pour Duniter #195

Nous avons une discussion sur #195 avec @cgeek, je pense qu’elle peut être intéressante pour ceux qui veulent avoir des repères sur les concepts de réseau, client, runtime, specs… Je vais essayer de poser quelques bases ici que j’aurais apprécié lire clairement dans la doc et qui ne m’étaient pas apparues clairement dans le post d’@elois Vocabulaire de base pour comprendre Duniter-v2s (lecture fortement recommandée pour tous).

Client

version elois

Le client est à la fois la “machine virtuelle” qui exécute le runtime, et le logiciel qui assura la connexion avec l’extérieur (couche p2p, API RPC, oracle de distance, keystore…). Le client inclut un runtime “natif” (mais on ne l’utilise pas sauf exception), plusieurs specs (par exemple duniter --chain=dev, duniter --chain=gdev pour un client compilé avec la feature gdev), cf le super post État des lieux des différentes chaînes.

Runtime

version elois

C’est à la fois un élément de l’état (state) en tant que “donnée binaire” et à la fois la fonction de transition d’état (state transition) interprétée en webassembly et exécutée à chaque bloc par le client pour passer d’un état au suivant. Elle peut être modifiée par la méthode system.setCode(runtime), elle-même faisant partie du runtime.

Spec

version hugo

La définition d’elois est très bien :stuck_out_tongue:. Pour plus d’infos, cf le code (je cite en simplifiant) :

duniter-polkadot-sdk/substrate/client/chain-spec/src/chain_spec.rs at c84530c57a6f9ab808a92f6b5d29338acf1c6e4f · duniter/duniter-polkadot-sdk · GitHub

pub struct ChainSpec {
	client_spec: ClientSpec,
	genesis: GenesisSource,
}

duniter-polkadot-sdk/substrate/client/chain-spec/src/chain_spec.rs at c84530c57a6f9ab808a92f6b5d29338acf1c6e4f · duniter/duniter-polkadot-sdk · GitHub

struct ClientSpec {
	name: String,
	id: String,
	chain_type: ChainType,
	boot_nodes: Vec<MultiaddrWithPeerId>,
	protocol_id: Option<String>,
	fork_id: Option<String>,
	properties: Option<Properties>,
	code_substitutes: BTreeMap<String, Bytes>,
}

duniter-polkadot-sdk/substrate/client/chain-spec/src/chain_spec.rs at c84530c57a6f9ab808a92f6b5d29338acf1c6e4f · duniter/duniter-polkadot-sdk · GitHub

enum GenesisSource {
	File(PathBuf),
	Binary(Cow<'static, [u8]>),
	Storage(Storage),
	GenesisBuilderApi(GenesisBuildAction, Vec<u8>),
}

Réseau

C’est la notion que me manquait au début. C’est à la fois simple car intuitif, et complexe car plein de concepts entrent en jeu.

Un réseau blockchain, c’est un ensemble de machines connectés ensemble qui exécutent le même code (runtime) en même temps. Un réseau peut avoir plusieurs clients différents. Ces clients peuvent être légèrement différents (version différente, architecture différente) ou même radicalement différents (autre implémentation dans un autre langage).

Le runtime d’un réseau peut évoluer au cours du temps (blocs de numéro croissant) lors des runtime upgrades. Ça reste le même réseau, mais toutes les machines se sont mises d’accord en même temps de changer leur fonction de transition d’état.

Une chose qui a pu vous perturber est qu’on a utilisé des noms contre-intuitifs pour les différents réseaux gdev. Par exemple gdev-700 et gdev-800 étaient des réseaux différents alors que gdev-801 est juste un autre nom pour le réseau gdev-800 qui a juste connu un runtime upgrade. On aurait pu (et on pourra à l’avenir) les appeler gdev-a, gdev-b etc pour les réseaux successifs et comme ça gdev-802 désignerait uniquement un runtime, et pas un réseau. Le même runtime pourrait être exécuté sur plusieurs réseaux.

subtilité

En fait, tous les clients n’exécutent pas forcément exactement le même runtime. Certains peuvent se risquer à exécuter un runtime différent (par exemple runtime natif plutôt que webassembly) du moment que la transition d’état est exactement la même. Mais c’est très découragé parce que si on trouve un résultat différent des autres, ils peuvent nous bannir ><


Voilà, j’espère que ça peut vous aider à vous y retrouver. En tout cas, même nous on est un peu perdus quand il s’agit de choisir le modèle de release >< (cf #195 pour plus de discussions).

Maintenant ces bases posées (moi aussi ça m’a fait du bien de tout remettre à plat, tout n’est pas limpide dans ma tête), je peux proposer un schéma de branches de release.

  • une branche master qui contient toutes les contributions
    • pas de numérotation du runtime
    • pas de numérotation du client
  • une branche par réseau (gdev-a, gdev-b, gtest, g1) qui contient uniquement ce qui est pertinent pour ce réseau mais pas ailleurs
    • les g1-data.json pour alimenter le genesis
    • les “raw chainspecs” intégrées au binaire pour alimenter la commande duniter --chain=<network>
    • les mises-à-jour successives des bootnodes dans les chainspecs au cours de l’évolution des nœuds publics
    • des commits de merge de master lorsque l’on veut publier un nouveau client pour ce réseau (donc informer les gens qui ont des nœuds sur ce réseau de la disponibilité d’une mise-à-jour)
    • des commits de numérotation de ce client intégrant le nom du réseau (ex 1.2.3-gtest)
  • des branches de release du runtime basées sur master
    • ces branches ne contiennent qu’un commit de numérotation du runtime et du client d’après ce runtime (ex 900 et 0.0.0-900)
    • elles mènent à la publication de trois runtimes, prêts à être utilisés pour des runtime upgrade sur chaque réseau (la seule différence entre le runtime gtest et g1 étant des constantes du runtime)
    • elles mènent à la publication d’une client non destiné à un réseau existant (pas de chainspecs) mais destiné uniquement à des réseaux locaux de dev pour des environnements de dev (duniter --dev) mais qui démarre en utilisant ce runtime (gdev, il ne devrait pas y avoir de raison d’utiliser un autre runtime pour du dev local)

Les avantages que je vois à ce schéma sont :

  • on met le minimum de choses sur les branches de réseau et on peut abandonner la branche si on abandonne le réseau
  • les numéros de version sont toujours clairs, on évite de se retrouver avec une version numérotée qui n’est pas ce à quoi on s’attend
  • on identifie clairement les binaires (et images docker associées) destinées à un usage réseau de celles dédiées à un usage de dev

Mais je veux bien des retours et je pense que @cgeek aussi vu que ça concerne un peu tous les devs de clients, forgerons, et administrateurs de noeud.

3 Likes

Cela me paraît très bien.

Le reseau local avec alice et bob n’est présent que sur la branche du runtime si je comprend bien. C’est un cas particulier ? Il n’est pas dans la branche des réseaux ?

Je continue la revue ici, du coup. Globalement c’est plus clair.

Une seule chose à redire : je ne vois pas trop l’intérêt d’avoir des branches gdev-a, gdev-b.

Pour moi on peut se contenter de gdev, gtest et g1. Il n’y a a priori pas deux gdev qui tournent en parallèle, et on n’est pas à l’abri non plus de rebooter une gtest régulièrement.

Les branches peuvent être écrasées au besoin.

1 Like

Tout à fait d’accord, on peut avoir une branche gdev qui est la gdev actuelle et des branches archive/gdev-a si jamais on veut garder des traces d’une gdev passée.

1 Like

Ok, je commence demain dans ce cas.

2 Likes

Est-ce qu’on veut vraiment commiter ces fichiers liés au Genesis ? Au final, à partir du moment où ces fichiers sont disponibles dans les releases GitLab, est-ce que ça ne suffirait pas ?

Ainsi les commits sur les branches de réseau contiendraient uniquement des modifications liées :

  • aux fichiers de paramétrage (gdev.yaml et gdev_client-specs.yaml)
  • aux numérotations du Runtime et du Client
  • commits de merge

Je peux ajouter une commande xtask pour télécharger les livrables d’une version (g1-data.json, specs et raw-specs) et les copier en local.

Je dis cela pour éviter que la CI n’ajoute des commits “techniques” contenant les fichiers de specs et n’alourdissent le dépôt pour rien, vu que les releases porteront déjà les fichiers. Ce sont plus des fichiers de données que véritablement du code source.

3 Likes

Tu as raison, pas besoin de commiter ça, c’est justement ce que je reprochais au dépôt polkadot-sdk qui est archi lourd.

1 Like

Pour réaliser ce tour de passe-passe il va falloir une condition : indiquer la version du Runtime du Genesis dans le nom de la branche de réseau. Par exemple : network/gdev-800.

C’est nécessaire car, pour un réseau donné, si l’on veut relivrer le client sans modifier le Genesis (et c’est obligatoire), alors c’est que ce dernier doit être dispoinble quelque part : et s’il n’est pas dans le code source, alors un bon candidat pour GitLab CI serait d’aller le chercher dans une release.

Typiquement : cette release aurait le nom du réseau + la version du 1er runtime. C’est un bon identifiant. Ainsi pour mon exemple il existerait une release gdev-800 contenant :

  • le fichier g1-data.json
  • le fichier de configuration du Genesis (gdev.yaml)
  • le Genesis (fichier gdev.json)
  • optionnellement : le Runtime initial

Puis, pour chaque release du Client, une release de la forme :

<reseau>-<version-du-runtime-courant>-<version-du-client>

Par exemple :

gdev-801-0.8.0

Ces release du Client ne contiendraient que les raw-specs résultantes de la fusion du Genesis et de la nouvelle version des ClientSpec, ainsi que le Runtime courant. Soit deux fichiers. Le Genesis serait retrouvé à partir du nom de la branche.

Ainsi, pas besoin de commiter le Genesis. OK pour toi @HugoTrentesaux ?

3 Likes

Joli tour de passe-passe ! Ça a le mérite de tout rendre explicite dans le nom de branche et le nom de release. On pourrait donc ajouter un mini texte dans la release pour informer sur sa nature. Par exemple :

This release gdev-800 was created for the gdev network with the gdev-800 runtime in its genesis. The files of this release are used in network/gdev-800 branch to be included in the genesis.

Et dans la branche du réseau

This branch network/gdev-800 is used to publish updates for the gdev-800 network. It’s using in its embedded chainspecs the genesis produced by the gdev-800 release combined with the new client specs.

Donc en résumé le nom de branche network/gdev-800 signifie que

  • cette branche est dédiée à un réseau donné (network)
  • ce réseau donné est identifié par le runtime de son genesis
  • ce runtime peut être trouvé dans le release gdev-800

Une autre manière aurait été de nommer la branche gdev et de fournir le nom du release racine utilisé par ce réseau en tant que variable d’environnement pour la CI (par exemple GDEV=gdev-800).

Mais s’il est facile d’extraire l’info du nom de branche, c’est encore plus transparent de faire comme ça, et on pourra également s’en servir pour les noms des images docker, ce qui permettra de savoir clairement quel réseau on rejoint.

1 Like

J’ai globalement terminé le travail pour la branche par réseau. Comme je n’avance pas très vite en ce moment, je propose qu’on découpe le travail en deux tickets.

Qu’en dis-tu ?

1 Like

Pas de problème à découper. Je peux toujours forcer un release en l’état actuel pour mettre à jour le réseau gdev si nécessaire. Quel serait le découpage en tickets ? Tu veux dire gérer les assets par release plutôt que dans un commit ?

De toute façon pour l’instant on n’a qu’un seul réseau donc les paquets debian et yunohost vont viser ce réseau quoi qu’il arrive.

Je veux dire que tu exprimais deux features : pouvoir faire des releases depuis une branche de réseau, et faire des releases depuis master.

Pour l’instant je n’ai pas couvert la feature “release depuis master”. Par contre la release depuis une branche réseau fonctionne comme prévu.

1 Like

Voilà : ticket #239 pour les releases depuis master.

J’essaie de terminer le #195 maintenant, peut-être d’ici la semaine prochaine (dernière de juin).

1 Like