<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matthieu Amoros</title>
    <description>The latest articles on DEV Community by Matthieu Amoros (@matthamoros).</description>
    <link>https://dev.to/matthamoros</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3798567%2F16b9e923-fc41-4786-a1d5-2aa5df25255d.png</url>
      <title>DEV Community: Matthieu Amoros</title>
      <link>https://dev.to/matthamoros</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matthamoros"/>
    <language>en</language>
    <item>
      <title>MycelK — Fase 1: NodeId, distancia XOR y Kbuckets</title>
      <dc:creator>Matthieu Amoros</dc:creator>
      <pubDate>Tue, 10 Mar 2026 15:52:20 +0000</pubDate>
      <link>https://dev.to/matthamoros/mycelk-fase-1-nodeid-distancia-xor-y-kbuckets-4pc1</link>
      <guid>https://dev.to/matthamoros/mycelk-fase-1-nodeid-distancia-xor-y-kbuckets-4pc1</guid>
      <description>&lt;p&gt;Vamos a crear crates separados para manejar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kademlia: Solo el algoritmo, así podemos hacer tests estáticos fácilmente&lt;/li&gt;
&lt;li&gt;Transport: Manejo de UDP/Tokio etc.&lt;/li&gt;
&lt;li&gt;Node: El nodo en sí, que usará los crates anteriores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Organizamos las carpetas, scaffoldeamos los crates y configuramos el crate "Node".&lt;br&gt;
"Node" es el crate central, le cambiamos el Cargo.toml para definir las dependencias.&lt;/p&gt;

&lt;p&gt;Un pequeño &lt;code&gt;cargo build&lt;/code&gt; para asegurarnos de no haber roto la config del Node.&lt;/p&gt;
&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;Normalmente uso Claude Code para acelerar ciertas implementaciones. Sé que si estás leyendo este blog vas a pensar: "Una IA programa esto en 15 minutos, eres un completo inútil".&lt;br&gt;
Como dice el filósofo, la aventura es el camino recorrido, no el destino (o algo así).&lt;/p&gt;

&lt;p&gt;Acá no vamos a usar IA para programar. La uso para formatear el blog, realizar investigaciones (best practices, documentación de las libs utilizadas, etc.) pero no va a escribir ni una línea de código en este proyecto. Así que les propongo código 100% orgánico y de vez en cuando no del todo optimizado.&lt;/p&gt;

&lt;p&gt;No me juzguen, simplemente tengo ganas de programar y de romperme la cabeza a la antigua.&lt;/p&gt;
&lt;h2&gt;
  
  
  Empezamos con Kademlia
&lt;/h2&gt;

&lt;p&gt;Primero lo primero: ¿cómo almacenamos el identificador de nodo? No hay u256 en Rust. Vamos a crear nuestra propia estructura &lt;code&gt;[u8; 32]&lt;/code&gt; (32x8 = 256):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nf"&gt;NodeId&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// 256 bits, nos pegamos a la spec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tests y más tests
&lt;/h2&gt;

&lt;p&gt;Ya sé que no soy un gurú del TDD, pero los tests no hacen daño. Vamos a cubrir las mecánicas básicas y le pediremos al amigo Claude que genere tests adicionales para desafiar mi código (dado que no tengo a nadie a mano para hacer peer review).&lt;/p&gt;

&lt;p&gt;Me gusta mucho usar IA para generar tests unitarios adicionales, me permite matar dos pájaros de un tiro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Si no logra generar tests coherentes sin más explicación que "Generate unittests for class/struct/mod XXXX", generalmente es porque el código no está suficientemente documentado&lt;/li&gt;
&lt;li&gt;A veces encuentra ángulos de tests que yo no había considerado&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Condición de salida fase 1
&lt;/h2&gt;

&lt;p&gt;Le pedí a Claude que organizara el roadmap que había escrito y que le agregara condiciones de salida para cada fase. Le pedí que fuera exigente y me tiró una condición entretenida para la primera fase:&lt;br&gt;
&lt;em&gt;voz de robot&lt;/em&gt;: &lt;strong&gt;Criterio de salida&lt;/strong&gt;: routing table correcta sobre 1000 nodos simulados en memoria.&lt;br&gt;
Nada menos.&lt;/p&gt;

&lt;p&gt;Entonces vamos, acá está mi función para determinar los nodos más cercanos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_closest_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.get_bucket_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;//Vamos a ver si tenemos suficientes nodes en el bucket objetivo, sino nos moveremos haciendo +/- 1&lt;/span&gt;
        &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;//Condiciones de parada cuando hayamos dado la vuelta&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;max_extension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt;
            &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_extension&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;//Hay que ordenarlos de nuevo si tenemos más que node_count para retornar los más cercanos&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.sort_by_cached_key&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="nf"&gt;.distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;closest_nodes&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Todos los tests pasan en verde de primera, excepto el terrible &lt;code&gt;get_closest_nodes_correctness_1000_nodes&lt;/code&gt;.&lt;br&gt;
Durante mi análisis me di cuenta de que con una cantidad de nodos inferior a 20, el sort funcionaba y el test pasaba.&lt;br&gt;
Tenemos entonces un problema en el manejo de n &amp;gt; 20, lo que corresponde a un routing map con más de 1 índice lleno.&lt;br&gt;
Mirando un poco la lógica, tengo un error de razonamiento en el sort final.&lt;/p&gt;

&lt;p&gt;Hay que ejecutar SIEMPRE &lt;code&gt;closest_nodes.sort_by_cached_key(|n| searched_node.distance(n));&lt;/code&gt; dado que dentro de un bucket los nodos no están ordenados por distancia sino por disponibilidad (vía ping, etc.).&lt;/p&gt;

&lt;p&gt;La condición de parada del while también está mal pensada (Matthieu por favor...), los buckets están ordenados respecto al &lt;strong&gt;owner&lt;/strong&gt;, no al &lt;strong&gt;searched_node&lt;/strong&gt;. Diferencia importante.&lt;/p&gt;

&lt;p&gt;Tenemos entonces dos maneras de resolverlo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un fullscan: Recorremos todos los nodos conocidos, sort, truncate, return.&lt;/li&gt;
&lt;li&gt;Optimizamos implementando varias etapas de scan:

&lt;ul&gt;
&lt;li&gt;Primero el bucket que corresponde al nodo buscado. Si tiene una cantidad de nodos igual o superior a la cantidad buscada, sort, truncate, return.&lt;/li&gt;
&lt;li&gt;Segunda parte: No tenemos la cantidad de nodos pedida en el centro. Miramos los buckets de 0 a &lt;em&gt;centro&lt;/em&gt;. Si tenemos la cantidad buscada, sort, truncate, return.&lt;/li&gt;
&lt;li&gt;Tercera parte: Todavía no tenemos lo que necesitamos, hay que explorar los buckets centro + k y paramos cuando tengamos la cantidad deseada o hayamos agotado los buckets. Sort, truncate, return.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dije al principio que quería aprender, así que no nos vamos a conformar con el full scan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_closest_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.get_bucket_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;//Primera pasada: Si tenemos lo que necesitamos en el bucket objetivo, sort, truncate, return y listo.&lt;/span&gt;
        &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;//Segunda pasada: Revisamos los nodos cercanos&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
                &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;//Tercera pasada: Chequeamos los siguientes&lt;/span&gt;
            &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
                &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.sort_by_cached_key&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="nf"&gt;.distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;closest_nodes&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y ahí tenemos derecho a un bonito output de terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;running 17 tests
test k_bucket::tests::add_myself ... ok
test k_bucket::tests::add_node_to_full_bucket_head_responds ... ok
test k_bucket::tests::add_same_node_twice ... ok
test k_bucket::tests::add_same_node_moved_to_tail ... ok
test k_bucket::tests::add_unknown_node_to_bucket ... ok
test k_bucket::tests::get_closest_nodes_returns_all_when_fewer_than_k ... ok
test k_bucket::tests::has_node_returns_none_when_absent ... ok
test k_bucket::tests::has_node_with_explicit_bucket_index ... ok
test node_id::tests::xor_identity ... ok
test node_id::tests::xor_symmetry ... ok
test k_bucket::tests::get_closest_nodes_never_exceeds_k ... ok
test k_bucket::tests::has_node_returns_some_when_present ... ok
test k_bucket::tests::get_bucket_index_basic ... ok
test k_bucket::tests::get_closest_nodes_correctness_1000_nodes ... ok
test node_id::tests::test_node_id_creation ... ok
test k_bucket::tests::get_closest_nodes_owner_never_in_result ... ok
test k_bucket::tests::get_closest_nodes_empty_routing_table ... ok

&lt;/span&gt;&lt;span class="gp"&gt;test result: ok. 17 passed;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0 failed&lt;span class="p"&gt;;&lt;/span&gt; 0 ignored&lt;span class="p"&gt;;&lt;/span&gt; 0 filtered out&lt;span class="p"&gt;;&lt;/span&gt; finished &lt;span class="k"&gt;in &lt;/span&gt;0.02s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;¡Fase 1 completada!&lt;/p&gt;

&lt;p&gt;En el próximo post nos atacaremos a la capa de transporte, ¡a jugar con los sockets!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>kademlia</category>
      <category>p2p</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>MycelK — Construyendo una mensajería p2p descentralizada from scratch</title>
      <dc:creator>Matthieu Amoros</dc:creator>
      <pubDate>Tue, 10 Mar 2026 15:42:49 +0000</pubDate>
      <link>https://dev.to/matthamoros/mycelk-construyendo-una-mensajeria-p2p-descentralizada-from-scratch-7fo</link>
      <guid>https://dev.to/matthamoros/mycelk-construyendo-una-mensajeria-p2p-descentralizada-from-scratch-7fo</guid>
      <description>&lt;h2&gt;
  
  
  MycelK — Construyendo una mensajería p2p descentralizada from scratch
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;(Traducción al español desde mi post original en francés)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Post 0 : el porqué&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Hace unos días tuve el placer de escuchar el podcast de Lex Fridman y su conversación con Pavel Durov (Telegram). Como siempre con los podcasts de Lex, las preguntas son pertinentes, las respuestas profundas, y los temas técnicos complejos suenan como poesia. Dos personas tan inteligentes conversando sobre temas complejos inevitablemente dan ganas de abrir el editor de código favorito y lanzarse en un proyecto utópico de mensajería descentralizada... (al menos ese fue el efecto que tuvo en mí).&lt;/p&gt;

&lt;p&gt;Pero al final, de lo que se habla es de encriptación end-to-end, protección de datos, responsabilidad de la empresa en moderar los contenidos compartidos, etc. Nada muy entretenido, y ¿por qué llegamos a eso? Porque los servidores le pertenecen a alguien, y ese alguien es responsable de lo que pasa ahí.&lt;/p&gt;

&lt;p&gt;Y ahí pensé: ¿qué pasaría si no hubiera servidores? ¿Sin responsables? Y de hecho, si Matthieu quiere hablar con Alice, ¿por qué necesitaría un intermediario?&lt;/p&gt;

&lt;p&gt;Entonces empecé a preguntarme: ¿se podría hacer de otra forma?&lt;/p&gt;




&lt;h2&gt;
  
  
  La idea
&lt;/h2&gt;

&lt;p&gt;Una mensajería que no dependa de ningún servidor central. Donde cada participante es a la vez cliente y nodo de la red. Donde cortar un servidor no corta las comunicaciones.&lt;/p&gt;

&lt;p&gt;No es una idea nueva — BitTorrent, IPFS, y decenas de otros proyectos llevan veinte años explorando este tema. Pero yo quería entender cómo funciona de verdad, no solo usar una librería que abstrae toda la complejidad.&lt;/p&gt;

&lt;p&gt;Ahí es donde entra Kademlia.&lt;/p&gt;




&lt;h2&gt;
  
  
  Por qué Kademlia
&lt;/h2&gt;

&lt;p&gt;Kademlia es un algoritmo publicado en 2002 por Maymounkov y Mazières (otra dupla de cabezas brillantes). Es la base de BitTorrent, de una parte de IPFS, y de muchas redes distribuidas/p2p. Su rol: permitir que cualquier nodo de la red encuentre cualquier otra información sin servidor central.&lt;/p&gt;

&lt;p&gt;El concepto clave es la definición de la distancia entre los nodos de la red (vía XOR, pero eso lo veremos después). Una métrica con propiedades matemáticas interesantes que garantizan la convergencia del routing y limitan la complejidad algorítmica de las búsquedas.&lt;/p&gt;

&lt;p&gt;No voy a explicar Kademlia en este post. Eso será el objeto de un artículo dedicado. Lo que quiero decir acá es que podría haber usado una lib que ya lo implementa. No lo hice. Lo implemento from scratch, porque así es como se aprende de verdad.&lt;/p&gt;




&lt;h2&gt;
  
  
  Por qué Rust y Tauri
&lt;/h2&gt;

&lt;p&gt;Desarrollo soluciones ERP para la agroindustria. Mi día a día es SQL Server, Python, integración con equipos industriales. Poco espacio para mis utopías de geek.&lt;/p&gt;

&lt;p&gt;Rust me llama la atención desde hace un rato por sus garantías de seguridad de memoria y su rendimiento. Un proyecto de red p2p — con concurrencia, timeouts, conexiones UDP — es exactamente el terreno donde esas garantías tienen sentido.&lt;/p&gt;

&lt;p&gt;Tauri me permite construir una interfaz desktop con React (el único stack que manejo en frontend) manteniendo el backend en Rust. Un solo binario, multiplataforma, sin Electron y sus 200MB de runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  El stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kademlia DIY          — el algoritmo, from scratch, sin libs
Tokio                 — runtime async Rust
WebRTC-rs             — p2p entre máquinas detrás de NAT
Ed25519               — identidad criptográfica
ChaCha20-Poly1305     — cifrado E2E (veremos cómo resulta)
Tauri 2 + React + MUI — interfaz desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Qué va a cubrir esta serie
&lt;/h2&gt;

&lt;p&gt;Dividí el proyecto en 7 fases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Core Kademlia&lt;/strong&gt; — NodeId, distancia XOR, routing table, k-buckets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transporte UDP + RPC&lt;/strong&gt; — mensajes, serialización, matching de peticiones/respuestas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Protocolo completo&lt;/strong&gt; — bootstrap, lookup iterativo, store/republish&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integración WebRTC&lt;/strong&gt; — conexión p2p real entre dos máquinas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mensajería&lt;/strong&gt; — identidad, cifrado E2E, mensajes offline&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tauri + React&lt;/strong&gt; — interfaz de usuario&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Web of Trust&lt;/strong&gt; — resistencia a ataques Sybil vía firmas Ed25519&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cada fase tendrá uno o más artículos. Voy a documentar las decisiones de arquitectura, los errores, los callejones sin salida. No es un tutorial, es más bien un diario de desarrollo.&lt;/p&gt;




&lt;h2&gt;
  
  
  MycelK
&lt;/h2&gt;

&lt;p&gt;El proyecto se llama &lt;strong&gt;MycelK&lt;/strong&gt;. Mycel como micelio — esa red subterránea de filamentos fúngicos que conecta los árboles entre sí sin centro, sin jerarquía, de manera resiliente. K como Kademlia.&lt;/p&gt;

&lt;p&gt;El código será open source en GitHub. El nombre está libre en &lt;a href="http://crates.io" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Este post existe porque escribir me obliga a clarificar mi pensamiento. Y quién sabe, quizás le dé visibilidad a otros proyectos futuros.&lt;/p&gt;

&lt;p&gt;Si trabajas en temas similares o tienes preguntas sobre las decisiones de arquitectura, los comentarios están abiertos.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Lo que sigue: una explicación de Kademlia para humanos, y después el código de la Fase 1.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>p2p</category>
      <category>tauri</category>
      <category>programming</category>
    </item>
    <item>
      <title>MycelK — Phase 1 : NodeId, XOR distance et Kbuckets</title>
      <dc:creator>Matthieu Amoros</dc:creator>
      <pubDate>Tue, 03 Mar 2026 20:46:24 +0000</pubDate>
      <link>https://dev.to/matthamoros/mycelk-phase-1-nodeid-xor-distance-et-routing-table-en-rust-35jo</link>
      <guid>https://dev.to/matthamoros/mycelk-phase-1-nodeid-xor-distance-et-routing-table-en-rust-35jo</guid>
      <description>&lt;p&gt;&lt;em&gt;Post 1 : scaffolding du workspace, NodeId, XOR distance, routing table — Phase 1 complète.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Structure du workspace
&lt;/h2&gt;

&lt;p&gt;On organise le projet en workspace Cargo avec trois crates séparés :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kademlia&lt;/code&gt;&lt;/strong&gt; — l'algo pur, zero I/O, facile à tester en isolation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;transport&lt;/code&gt;&lt;/strong&gt; — UDP/Tokio, tout ce qui touche au réseau&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;node&lt;/code&gt;&lt;/strong&gt; — le nœud à proprement parler, qui consomme les deux autres&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;node&lt;/code&gt; est la crate centrale. On configure son &lt;code&gt;Cargo.toml&lt;/code&gt; pour déclarer les dépendances locales, puis un &lt;code&gt;cargo build&lt;/code&gt; pour vérifier qu'on n'a rien cassé.&lt;/p&gt;




&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;D'habitude j'utilise Claude Code pour accélérer certaines implémentations. Je sais que si tu lis ce blog tu vas te dire : &lt;em&gt;"Une IA code ça en 15min, t'es vraiment un gros naze"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Comme dit le philosophe, le plus important parfois c'est le chemin parcouru pas la destination.&lt;/p&gt;

&lt;p&gt;Ici on n'utilisera pas d'IA pour coder. Je l'utilise pour formater le blog, faire des recherches (best practices, doc des libs, etc.), mais elle n'écrira aucune ligne de code sur ce projet. Du code 100% organique, forcément pas toujours optimisé.&lt;/p&gt;

&lt;p&gt;Me jugez pas, j'ai juste envie de programmer et de me prendre la tête à l'ancienne.&lt;/p&gt;




&lt;h2&gt;
  
  
  NodeId
&lt;/h2&gt;

&lt;p&gt;Premier problème concret : comment stocker un identifiant de nœud en Rust ?&lt;/p&gt;

&lt;p&gt;Pas de &lt;code&gt;u256&lt;/code&gt; natif. On crée notre propre structure — un newtype autour d'un tableau de 32 octets (32 × 8 = 256 bits, conforme à la spec Kademlia) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nf"&gt;NodeId&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple. Ça sérialise bien sur le réseau, ça tient dans une copie, et on garde la porte ouverte pour des implémentations futures sans casser l'interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  Des tests et encore des tests
&lt;/h2&gt;

&lt;p&gt;Je suis pas un gourou du TDD, mais des tests ça fait pas de mal. On couvre les mécaniques de base, et j'utilise Claude pour générer des tests additionnels qui challengent mon code — vu que j'ai personne sous la main pour faire du peer review.&lt;/p&gt;

&lt;p&gt;J'aime beaucoup cette approche pour deux raisons :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Si l'IA n'arrive pas à générer des tests cohérents sans plus d'explications que &lt;code&gt;"Generate unittests for struct NodeId"&lt;/code&gt;, c'est souvent parce que le code n'est pas assez documenté.&lt;/li&gt;
&lt;li&gt;Elle trouve parfois des angles de tests auxquels je n'avais pas pensé.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Condition de sortie Phase 1
&lt;/h2&gt;

&lt;p&gt;J'avais écrit une roadmap, j'ai demandé à Claude de l'organiser et d'y ajouter des critères de sortie exigeants pour chaque phase. Le résultat pour la Phase 1 :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Critère de sortie&lt;/strong&gt; : routing table correcte sur 1000 nœuds simulés en mémoire.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rien que ça. On y va.&lt;/p&gt;




&lt;h2&gt;
  
  
  get_closest_nodes — première tentative
&lt;/h2&gt;

&lt;p&gt;Voici ma première implémentation pour trouver les nœuds les plus proches d'un &lt;code&gt;NodeId&lt;/code&gt; cible :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_closest_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.get_bucket_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// On regarde d'abord le bucket cible, puis on s'étend par +/- 1&lt;/span&gt;
    &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;max_extension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_extension&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.sort_by_cached_key&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="nf"&gt;.distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;closest_nodes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tous les tests passent au vert sauf un : &lt;code&gt;get_closest_nodes_correctness_1000_nodes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;En analysant, je remarque que le test passe avec moins de 20 nœuds. Le bug se manifeste dès qu'on a plusieurs buckets non vides. Deux erreurs de raisonnement dans le code :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Erreur 1&lt;/strong&gt; — Le tri final est conditionnel (&lt;code&gt;if closest_nodes.len() &amp;gt; node_count&lt;/code&gt;). Mais dans un bucket, les nœuds sont ordonnés par disponibilité (via ping), pas par distance XOR. Le tri doit être &lt;strong&gt;toujours&lt;/strong&gt; exécuté.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Erreur 2&lt;/strong&gt; — La condition d'arrêt du &lt;code&gt;while&lt;/code&gt; est fausse. Les buckets sont indexés par rapport au &lt;strong&gt;owner&lt;/strong&gt; du nœud local, pas par rapport au &lt;code&gt;searched_node&lt;/code&gt;. L'expansion symétrique autour de &lt;code&gt;center&lt;/code&gt; ne correspond pas à une expansion par distance XOR croissante.&lt;/p&gt;




&lt;h2&gt;
  
  
  get_closest_nodes — version corrigée
&lt;/h2&gt;

&lt;p&gt;Deux approches possibles :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fullscan&lt;/strong&gt; — parcourir tous les nœuds connus, trier, truncate, return.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scan en plusieurs passes&lt;/strong&gt; — optimiser en explorant d'abord le bucket cible, puis les buckets inférieurs, puis les supérieurs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;J'aurais pu prendre le fullscan. Mais on est là pour apprendre, donc :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_closest_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.get_bucket_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NodeId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Première passe : bucket cible&lt;/span&gt;
    &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Deuxième passe : buckets 0..center&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Troisième passe : buckets center+1..255&lt;/span&gt;
        &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;node_count&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.routing_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Tri XOR + truncate — toujours, sans condition&lt;/span&gt;
    &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.sort_by_cached_key&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;searched_node&lt;/span&gt;&lt;span class="nf"&gt;.distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;closest_nodes&lt;/span&gt;&lt;span class="nf"&gt;.truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;closest_nodes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Résultat
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;running 17 tests
test k_bucket::tests::add_myself ... ok
test k_bucket::tests::add_node_to_full_bucket_head_responds ... ok
test k_bucket::tests::add_same_node_twice ... ok
test k_bucket::tests::add_same_node_moved_to_tail ... ok
test k_bucket::tests::add_unknown_node_to_bucket ... ok
test k_bucket::tests::get_closest_nodes_returns_all_when_fewer_than_k ... ok
test k_bucket::tests::has_node_returns_none_when_absent ... ok
test k_bucket::tests::has_node_with_explicit_bucket_index ... ok
test node_id::tests::xor_identity ... ok
test node_id::tests::xor_symmetry ... ok
test k_bucket::tests::get_closest_nodes_never_exceeds_k ... ok
test k_bucket::tests::has_node_returns_some_when_present ... ok
test k_bucket::tests::get_bucket_index_basic ... ok
test k_bucket::tests::get_closest_nodes_correctness_1000_nodes ... ok
test node_id::tests::test_node_id_creation ... ok
test k_bucket::tests::get_closest_nodes_owner_never_in_result ... ok
test k_bucket::tests::get_closest_nodes_empty_routing_table ... ok

&lt;/span&gt;&lt;span class="gp"&gt;test result: ok. 17 passed;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0 failed&lt;span class="p"&gt;;&lt;/span&gt; 0 ignored&lt;span class="p"&gt;;&lt;/span&gt; 0 measured&lt;span class="p"&gt;;&lt;/span&gt; 0 filtered out&lt;span class="p"&gt;;&lt;/span&gt; finished &lt;span class="k"&gt;in &lt;/span&gt;0.02s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;17/17. Phase 1 terminée.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;La suite : couche transport UDP avec Tokio. À nous les sockets.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>kademlia</category>
      <category>p2p</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Venez voir, c'est bien.</title>
      <dc:creator>Matthieu Amoros</dc:creator>
      <pubDate>Sun, 01 Mar 2026 02:50:42 +0000</pubDate>
      <link>https://dev.to/matthamoros/venez-voir-cest-bien-50jo</link>
      <guid>https://dev.to/matthamoros/venez-voir-cest-bien-50jo</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/matthamoros" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3798567%2F16b9e923-fc41-4786-a1d5-2aa5df25255d.png" alt="matthamoros"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/matthamoros/mycelk-construire-une-messagerie-p2p-decentralisee-from-scratch-460l" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;MycelK — Construire une messagerie p2p décentralisée from scratch&lt;/h2&gt;
      &lt;h3&gt;Matthieu Amoros ・ Feb 28&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#p2p&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#rust&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#tauri&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#programming&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>p2p</category>
      <category>rust</category>
      <category>tauri</category>
      <category>programming</category>
    </item>
    <item>
      <title>MycelK — Construire une messagerie p2p décentralisée from scratch</title>
      <dc:creator>Matthieu Amoros</dc:creator>
      <pubDate>Sat, 28 Feb 2026 22:57:04 +0000</pubDate>
      <link>https://dev.to/matthamoros/mycelk-construire-une-messagerie-p2p-decentralisee-from-scratch-460l</link>
      <guid>https://dev.to/matthamoros/mycelk-construire-une-messagerie-p2p-decentralisee-from-scratch-460l</guid>
      <description>&lt;p&gt;&lt;em&gt;Post 0 : le pourquoi&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;J'ai eu le plaisir d'écouter il y a quelques jours le podcast de Lex Fridman et sa conversation avec Pavel Durov (Telegram). Comme d'habitude avec les podcasts de Lex, les questions sont pertinentes, les réponses profondes et les sujets techniques complexes sonnent comme de la prose. Deux personnes aussi intelligentes qui discutent de sujets complexes, ça donne forcément envie d'ouvrir son éditeur de code favori et de se lancer dans un projet utopique de messagerie décentralisée... (En tout cas c'est l'effet que ça a eu sur moi).&lt;/p&gt;

&lt;p&gt;Mais du coup on parle de l'encryption end-to-end, de la protection des données, la responsabilité de l'entreprise de modérer les contenus partagés etc. Rien de bien fun et au final pourquoi on doit en arriver là ? Parce que les serveurs appartiennent à quelqu'un, et ce quelqu'un en est responsable.&lt;/p&gt;

&lt;p&gt;Et là boum, l'idée : et si il n'y avait pas de serveurs ? Pas de responsables ? Et d'ailleurs si Matthieu veut parler à Alice, pourquoi il aurait besoin d'un intermédiaire ?&lt;/p&gt;

&lt;p&gt;Alors j'ai commencé à me demander : est-ce qu'on pourrait faire autrement ?&lt;/p&gt;




&lt;h2&gt;
  
  
  L'idée
&lt;/h2&gt;

&lt;p&gt;Une messagerie qui ne dépend d'aucun serveur central. Où chaque participant est à la fois client et nœud du réseau. Où couper un serveur ne coupe pas les communications.&lt;/p&gt;

&lt;p&gt;Ce n'est pas une idée nouvelle — BitTorrent, IPFS, et des dizaines d'autres projets explorent ce sujet depuis vingt ans. Mais je voulais comprendre comment ça fonctionne vraiment, pas juste utiliser une bibliothèque qui abstrait toute la complexité.&lt;/p&gt;

&lt;p&gt;C'est là que Kademlia entre en jeu.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pourquoi Kademlia
&lt;/h2&gt;

&lt;p&gt;Kademlia est un algorithme publié en 2002 par Maymounkov et Mazières (encore des sacrés cerveaux). C'est la fondation de BitTorrent, d'une partie d'IPFS, et de nombreux réseaux distribués/p2p. Son rôle : permettre à n'importe quel nœud du réseau de trouver n'importe quelle autre information sans serveur central.&lt;/p&gt;

&lt;p&gt;Le concept clé est la définition de la distance entre les nœuds du réseau (via un XOR, mais bref on en parlera plus tard). Une métrique qui a des propriétés mathématiques sympas garantissant la convergence du routing et limitant la complexité algorithmique des recherches.&lt;/p&gt;

&lt;p&gt;Je ne vais pas expliquer Kademlia dans ce post. Ce sera l'objet d'un article dédié. Ce que je veux dire ici, c'est que j'aurais pu utiliser une lib qui l'implémente déjà. Je ne l'ai pas fait. Je l'implémente from scratch, parce que c'est comme ça qu'on apprend vraiment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pourquoi Rust et Tauri
&lt;/h2&gt;

&lt;p&gt;Je développe des solutions ERP pour l'agroindustrie. Mon quotidien c'est SQL Server, Python, de l'intégration avec des équipements industriels. Pas trop d'espace pour mes utopies de geek du coup.&lt;/p&gt;

&lt;p&gt;Rust m'intéresse depuis un moment pour ses garanties de sécurité mémoire et ses performances. Un projet réseau p2p — avec de la concurrence, des timeouts, des connexions UDP — est exactement le terrain où ces garanties ont du sens.&lt;/p&gt;

&lt;p&gt;Tauri me permet de construire une interface desktop avec React (la seule stack que je maîtrise en frontend) tout en gardant le backend en Rust. Un seul binaire, multiplateforme, sans Electron et ses 200MB de runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  La stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kademlia DIY          — l'algo, from scratch, zero lib
Tokio                 — runtime async Rust
WebRTC-rs             — p2p entre machines derrière NAT
Ed25519               — identité cryptographique
ChaCha20-Poly1305     — chiffrement E2E (on va voir ce que ça donne)
Tauri 2 + React + MUI — interface desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Ce que cette série va couvrir
&lt;/h2&gt;

&lt;p&gt;J'ai découpé le projet en 7 phases :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Core Kademlia&lt;/strong&gt; — NodeId, XOR distance, routing table, k-buckets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transport UDP + RPC&lt;/strong&gt; — messages, sérialisation, matching requêtes/réponses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Protocole complet&lt;/strong&gt; — bootstrap, lookup itératif, store/republish&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Intégration WebRTC&lt;/strong&gt; — connexion p2p réelle entre deux machines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Messagerie&lt;/strong&gt; — identité, chiffrement E2E, messages offline&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tauri + React&lt;/strong&gt; — interface utilisateur&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Web of Trust&lt;/strong&gt; — résistance aux attaques Sybil via signatures Ed25519&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Chaque phase fera l'objet d'un ou plusieurs articles. Je documenterai les décisions d'architecture, les erreurs, les dead ends. Pas un tutoriel mais plutot un journal de bord.&lt;/p&gt;




&lt;h2&gt;
  
  
  MycelK
&lt;/h2&gt;

&lt;p&gt;Le projet s'appelle &lt;strong&gt;MycelK&lt;/strong&gt;. Mycel comme mycélium — ce réseau souterrain de filaments fongiques qui connecte les arbres entre eux sans centre, sans hiérarchie, de manière résiliente. K comme Kademlia.&lt;/p&gt;

&lt;p&gt;Le code sera open source sur GitHub. Le nom est libre sur &lt;a href="http://crates.io" rel="noopener noreferrer"&gt;crates.io&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Ce post existe parce qu'écrire me force à clarifier ma pensée. Et puis qui sait, peut-être que ça donnera de la visibilité à d'autres futurs projets.&lt;/p&gt;

&lt;p&gt;Si tu travailles sur des sujets similaires ou si tu as des questions sur les choix d'architecture, les commentaires sont ouverts.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;La suite : une explication de Kademlia pour des humains, puis le code de la Phase 1.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>p2p</category>
      <category>rust</category>
      <category>tauri</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
