# Le Runtime et les Externalités

**URL:** https://forum.duniter.org/t/le-runtime-et-les-externalites/11408
**Category:** Duniter-v2s
**Tags:** learn, substrate, externalities, onboarding, runtime
**Created:** [13 July 2023 16:52 UTC](https://forum.duniter.org/t/le-runtime-et-les-externalites/11408 "2023-07-13T16:52:34Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![cgeek](https://forum.duniter.org/user_avatar/forum.duniter.org/cgeek/32/279_2.png) [@cgeek](https://forum.duniter.org/u/cgeek)
#### Post date: [13 July 2023 16:52 UTC](https://forum.duniter.org/t/le-runtime-et-les-externalites/11408/1 "2023-07-13T16:52:34Z")

</div>

Il y a quelques jours je découvrais le concept d’[Externalités](https://forum.duniter.org/t/discussions-autour-du-sujet-vocabulaire-de-base-pour-comprendre-duniter-v2s/9063/34). Comprendre ce point m’a aidé à mieux cerner le Runtime, son environnement et ses limites.

Je fais ce petit sujet pour vous partager mon apprentissage. J’espère que vous en tirerez profit. Et puis je pourrais moi aussi m’y référer plus tard si besoin. 🙂

> ⚠ Sujet très technique. 🧑‍💻 exprimé avec mes mots et ma compréhension actuelle.

## Que s’est-il passé ?

À la base, j’étais simplement en train de creuser un peu ce qui se trouvait dans le Storage dans un bête test unitaire. Et très souvent sur un test unitaire, on tombe sur ce genre de code :

```rust
#[test]
fn simple_test() {
    new_test_ext().execute_with(|| {
        // [...] code interagissant avec le Runtime
    });
}

```

Ici, le code qui m’a posé question est `execute_with`. J’ai tenté, un peu par hasard, de mettre des appels pour écrire dans le storage avant ou après, par exemple : `System::set_block_number(1);`, et j’ai reçu comme réponse à l’exécution :

> panicked at ‘`set_version_1` called **outside of an Externalities-provided environment**.’

Et c’est cette erreur qui m’a posé question. Je ne comprenais pas pourquoi il était impossible de mettre un pauvre appel de fonction de pallet à cet endroit.

C’est le point de départ de mes recherches sur les Externalities. J’ai donc ouvert mon débogueur sur Duniter V2S, une autre fenêtre d’IDE sur le code source de Substrate, et j’ai creusé. Voici ce qu’il en est sorti.

## Les limites du Runtime

Au final, c’est autour de ce point que tout à tourné : la frontière entre Runtime et Client.

Il faut comprendre que le Runtime c’est le cœur du système, au sens où c’est l’endroit où sont définies les règles communes à tous les utilisateurs de la blockchain et qui _doit faire consensus_.

Or, pour faire consensus il faut exprimer des règles **sans ambiguïtés** : c’est un exercice délicat où il est très facile de tomber dans le piège d’une spécification interminable voire infinie.

Imaginez simplement : dans le protocole Duniter nous souhaitons stocker des comptes, et un nombre d’unités de monnaie que celui-ci possède. Bien. Où stocke-t-on ces données ? Dans une “base de données”. D’accord mais quelle base précisément ? Et quelle version du logiciel pour cette base ? Et quel protocole pour cette base de donnée ? Et puis, cette base fonctionne elle-même sur un OS, lequel ? Compilé sur quelle architecture ? Et d’ailleurs c’est quoi une architecture ? Etc.

Sans être parfaitement clair sur toutes ces questions, alors il y a toutes les chances que le consensus ne tienne pas car les résultats obtenus chez les uns ne seront pas les mêmes chez les autres.

### Une solution : l’agnosticité

C’est-à-dire admettre qu’il existe certains élément échappant à une définition déterministe rigoureuse. Le protocole définit alors plutôt des principes à respecter pour des éléments extérieurs au Runtime, et _suppose_ que ces principes seront respectés.

Un bon exemple, je trouve, est le Storage. Sa structure est parfaitement définie : il s’agit d’un Merkle Patricia tree en Base 16, les règles de stockage sont déterministes.

Mais ! C’est là où se trouve l’hypothèse : ce Trie repose sur une base de données clé/valeur, certes, mais on ne dit pas laquelle. Le protocole est agnostique sur ce point, l’implémentation est laissée au Client (pas au Runtime) qui peut en changer à volonté. D’ailleurs selon la façon dont vous compilez votre nœud, vous pourriez utiliser RocksDB ou bien PartityDB. Et c’est _censé_ ne rien changer.

Ces hypothèses, axiomes, **agnosticités, …, ont un nom dans Substrate : ce sont des Externalités.**

### Les Externalités

Le Storage est la plus emblématique des Externalités, mais il en existe d’autres :

- les offchain workers : le Runtime _sait_ qu’il peut les solliciter, sans savoir exactement (i.e. sans déterminisme) l’impact que cet appel aura sur la suite des évènements ;
- le transaction pool : le Runtime _sait_ qu’il peut pousser un extrinsic ;
- le keystore : le Runtime _sait_ qu’il peut obtenir un trousseau cryptographique pour générer des blocs ;
- …

Ces externalités sont exploitées depuis le Runtime via des _runtime interface_ (RI), déclarées par la macro `#[runtime_interface]` : ce sont des modules Rust un peu spécifiques possédant la particularité de _nécessiter un environnement fournissant les Externalités_ que ces RI vont utiliser. Exemples :

- module `sp_io::storage` : RI tellement central qu’il n’utilise pas d’extension
- module `sp_io::misc` : RI qui utilise l’extension ReadRuntimeVersionExt
- module `sp_io::crypto` : RI qui utilise l’extension KeystoreExt, VerificationExtDeprecated
- module `sp_io::offchain` : RI qui utilise OffchainWorkerExt, TransactionPoolExt, OffchainDbExt
- module `sp_io::statement_store` : RI qui utilise StatementStoreExt

> ℹ Vous noterez que tous ces modules sont définis dans `sp_io`, c’est effectivement cette crate qui expose le runtime interface et fait le pont vers le Client depuis le Runtime.

Pour le dire autrement : au moment où l’on appelle l’une de ces RI (le module sp\_io::storage par exemple), _les externalités doivent avoir été mises à disposition_ de cette RI afin que celle-ci puisse les manipuler.

> Je me suis demandé “pourquoi ?” avoir conçu ce système, puis en y réfléchissant un peu je pense que c’est dû au Runtime : pour lui, il n’existe qu’un seul `storage`, qu’un seul fournisseur de `crypto` ou autre `offchain`. Et donc la transition avec le monde Rust se fait de cette façon : via les runtime interface, qui  
> du point de vue de la compilation sont comme des variables d’instance globales (ce qui n’existe normalement pas en Rust).
> 
> A noter que c’est très pratique pour les tests unitaires : le Runtime a beau être commun à tous les tests, les données sont à chaque test différentes car l’on injecte pas les mêmes externalités via la fonction `execute_with(|| { ... })`. CQFD ? 🙂
> 
> Non car en fait à part pour les tests, si l’on réinjecte les externalités à chaque appel au Runtime c’est parce que celui-ci peut changer : eh oui ! Les fameuses Runtime Upgrade, changement à chaud du Runtime.

Pour reprendre mon exemple de départ `System::set_block_number(1)`, voici ce qui se passe :

#### Si l’appel est fait dans ` new_test_ext().execute_with()`

1. `execute_with` injecte les externalités ( **une** _instance_ de storage notamment) dans le thread courant
2. `System::set_block_number` (une fonction _dans le Runtime de test_) appelle la méthode “`sp_io::storage::set`”, où `sp_io::storage` est donc un module construit par la macro `#[runtime_interface]` évoquée précédemment, et est une sorte d’objet global au Runtime représentant le Storage ;
3. `sp_io::storage::set` appelle alors `sp_externalities::with_externalities` pour exécuter la fonction `::set` **dans un contexte où le mot-clé `self` (c’est du Rust) est l’objet porteur des externalités** (c’est l’injection proprement dite), précisément celui défini à l’étape 1, et donc la fonction `::set` peut faire l’appel `self.set_storage(key.to_vec(), value.to_vec())`
4. qui elle-même fait appel à `self.place_storage(key.to_vec(), value.to_vec())`
5. `self` est typiquement un objet [BasicExternalities](https://paritytech.github.io/substrate/master/sp_state_machine/struct.BasicExternalities.html), donc l’appel mène à `BasicExternalities::place_storage` qui va réaliser l’écriture dans l’Overlay
6. La donnée est dans le cache (l’Overlay), elle sera inscrite dans le Storage à la finalisation du bloc

#### Si l’appel est fait avant `new_test_ext().execute_with()`

On ne fait rien à l’étape 1. Puis à l’étape 2, `#[runtime_interface]` plante avec le message :

> panicked at ‘`set_version_1` called **outside of an Externalities-provided environment**.’

C’est en fait cette macro qui remonte la panic au Runtime, car à l’exécution, cette macro vérifie que les externalités ont bien été fournies. Ce qui n’est pas le cas _avant_ l’appel à `execute_with()` !

#### Si l’appel est fait après ` new_test_ext().execute_with()`

Même comportement que si l’appel était fait avant.

C’est parce que `new_test_ext().execute_with()` supprime la référence globale aux externalities une fois exécutée.

### L’instruction #[runtime\_interface]

On le comprend bien à ce stade, c’est la macro exploitée dans la crate `sp_io` qui fait le boulot de dialogue entre Runtime et Client, que le Runtime soit en Wasm ou en Natif.

Si l’on regarde le détail de cette macro, on verrait que celle-ci génère à la fois du code pour le Runtime natif et le Runtime Wasm. C’est grâce à cela que le nœud Duniter V2S peut exécuter les externalités aussi bien depuis un Runtime natif que Wasm (ce qui est important, car notre nœud peut aussi bien exécuter un Runtime natif que Wasm).

### Ecriture finale dans le Storage

Au final, l’instruction `System::set_block_number(1)` écrit une valeur dans le storage au moment de l’appel à `Executive::finalize_block` dans le Client. Ce dernier demande au Runtime de finaliser le bloc, qui lui-même appelle l’externalité `sp_io::storage` pour écrire la valeur au moment du calcul du Merkle root.

Pour les plus curieux, vous pouvez mettre un point d’arrêt ici (crate sp\_io) :

```auto
#[version(2)]
fn root(&mut self, version: StateVersion) -> Vec<u8> {
	self.storage_root(version)
}

```

Et lancer :

```
cargo run -- --dev

```

Ou encore plus profondément ici (crate sp\_state\_machine, fichier trie\_backend\_essance.rs) :

```auto
fn insert(&mut self, prefix: Prefix, value: &[u8]) -> H::Out {
	HashDB::insert(self.overlay, prefix, value)
}

```

Vous pourrez constater que la 1ère fois que l’on passe dans cette méthode, c’est lors du calcul du storage\_root, lui-même appelé à la finalisation du bloc.

## Conséquence : certains Runtime Upgrade pourraient nécessiter une màj du Client

… si l’une des externalités devait être modifiée pour convenir aux nouvelles spécifications du Runtime.

Par exemple, si le Storage changeait de forme (passait d’un Merkle Patricia tree Base 16 à autre chose). Ce qui est quand même assez peu probable 🙂

Mais c’est logique : les externalités, en tant qu’axiomes, fondent le fonctionnement du Runtime et donc ne doivent pas vraiment bouger.

On comprend bien que si le modèle de donnée changeait la migration devrait passer par un hard fork. 🌩

## Pour résumer

```rust
#[test]
fn simple_test() {
    new_test_ext().execute_with(|| {
        // [...] code interagissant avec le Runtime
    });
}

```

`execute_with(|| { ... })` injecte dans une variable globale les externalités présentes dans `new_test_ext()`, variable globale qui sera réutilisée par les runtime interface lorsque celles-ci sont appelées par le Runtime.

En dehors de ce contexte, les runtime interface paniquent :

> panicked at ‘`set_version_1` called **outside of an Externalities-provided environment**.’

---

<div class="post-metadata">

### Author: ![tuxmain](https://forum.duniter.org/user_avatar/forum.duniter.org/tuxmain/32/6423_2.png) [@tuxmain](https://forum.duniter.org/u/tuxmain)
#### Post date: [13 July 2023 18:08 UTC](https://forum.duniter.org/t/le-runtime-et-les-externalites/11408/2 "2023-07-13T18:08:10Z")

</div>

`runtime-interface` est tout un tas de macros qui facilite grandement l’interfaçage entre le runtime WASM et l’exécuteur. Il construit une interface rustique sur le protocole WASM qui est un bête FFI très rudimentaire (le WASM déclare ses fonctions appelables, dont l’argument n’est qu’une slice `&[u8]`, et il peut appeler les fonctions de l’hôte dont la signature est définie comme une FFI C avec `#[extern]`).

Pour [un projet utilisant WASM](https://git.txmn.tk/tuxmain/cultivar) j’avais commencé avec les crates les plus bas niveau pour exécuter du WASM (wasmtime), puis en constatant la difficulté de faire un truc propre et complet je suis remonté progressivement vers des crates qui gèrent toute la plomberie, donc `sp-runtime-interface` et tout.

---

<div class="post-metadata">

### Author: ![cgeek](https://forum.duniter.org/user_avatar/forum.duniter.org/cgeek/32/279_2.png) [@cgeek](https://forum.duniter.org/u/cgeek)
#### Post date: [13 July 2023 18:17 UTC](https://forum.duniter.org/t/le-runtime-et-les-externalites/11408/3 "2023-07-13T18:17:55Z")

</div>

Intéressant, mais je n’ai pas encore vu/creusé les appels Client → Runtime. Pour moi les runtime\_interface c’est uniquement dans l’autre sens.

---

<div class="post-metadata">

### Author: ![vit](https://forum.duniter.org/user_avatar/forum.duniter.org/vit/32/2775_2.png) [@vit](https://forum.duniter.org/u/vit)
#### Post date: [15 July 2023 07:35 UTC](https://forum.duniter.org/t/le-runtime-et-les-externalites/11408/4 "2023-07-15T07:35:17Z")

</div>

Tres interessant !

Pour les connaisseurs de l’architecture hexagonale, on retrouve les concepts d’injection de dépendances et d’inversion de dépendances à l’aide d’interfaces. Ces concepts utilisés en programmation orientée objet, sont ici adaptés à un langage non orienté objet,

---

<div class="post-metadata">

### Author: ![cgeek](https://forum.duniter.org/user_avatar/forum.duniter.org/cgeek/32/279_2.png) [@cgeek](https://forum.duniter.org/u/cgeek)
#### Post date: [15 July 2023 17:50 UTC](https://forum.duniter.org/t/le-runtime-et-les-externalites/11408/5 "2023-07-15T17:50:24Z")

</div>

J’ai creusé un peu et je note que l’on peut repérer les bords du Runtime via :

- la macro `#[runtime_interface]` pour gérer les appels depuis le Runtime vers son hôte
- la macro `impl_runtime_apis!` pour gérer les appels depuis l’hôte vers le Runtime : code côté Duniter, fichier [apis.rs](https://git.duniter.org/nodes/rust/duniter-v2s/-/blob/master/runtime/common/src/apis.rs). On y voit bien les appels `Executive::` qui laisse présager les appels à l’exécutor ensuite, qui gèrera la bifurcation vers natif ou Wasm.

Ces deux macros génèrent à la fois du code côté client et du code côté Runtime (Wasm et Natif) pour faire le pont entre les deux mondes.

> [@tuxmain](#):
>
> Il construit une interface rustique sur le protocole WASM qui est un bête FFI très rudimentaire (le WASM déclare ses fonctions appelables, dont l’argument n’est qu’une slice `&[u8]`, et il peut appeler les fonctions de l’hôte dont la signature est définie comme une FFI C avec `#[extern]`).

Oui c’est ce que j’ai compris.

Je suis allé voir un peu la définition du standard WebAssembly, c’est frappant de voir que les contraintes posées au départ son très similaires à celles que l’on peut rencontrer pour un protocole blockchain (sandboxé et déterministe notamment).

Je ne sais pas si c’est juste une coïncidence. Mais WebAssembly est de ce fait particulièrement adapté à être un Runtime pour blockchain (indépendamment de Substrate je veux dire).
