<?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: Pedro Pietroluongo</title>
    <description>The latest articles on DEV Community by Pedro Pietroluongo (@pedropietro).</description>
    <link>https://dev.to/pedropietro</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F357777%2F19250366-830f-405f-9ee0-b52fb7e5ec79.jpg</url>
      <title>DEV Community: Pedro Pietroluongo</title>
      <link>https://dev.to/pedropietro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pedropietro"/>
    <language>en</language>
    <item>
      <title>Os exemplos do repositório</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:04:47 +0000</pubDate>
      <link>https://dev.to/pedropietro/os-exemplos-do-repositorio-170o</link>
      <guid>https://dev.to/pedropietro/os-exemplos-do-repositorio-170o</guid>
      <description>&lt;p&gt;Na série IPFS com o Nabu, os posts anteriores abriram o nó por dentro. Este fecha a série rodando os programas do módulo &lt;code&gt;examples/&lt;/code&gt; deste repositório. O &lt;a href="https://github.com/Peergos/nabu-chat" rel="noopener noreferrer"&gt;nabu-chat&lt;/a&gt; é outro projeto. Aqui o código é &lt;code&gt;examples/src/main/java/examples/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;Ler &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; mostra as peças. A proposta dos exemplos é ver duas dessas peças com um CID (Content Identifier, o identificador do conteúdo) na mão: um processo grava, o outro lê. Cada programa de linha de comando faz um só trabalho, em cima de um nó compartilhado. CLI significa command-line interface, o programa que se executa no terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DemoNode&lt;/code&gt; sobe esse nó. Gera uma identidade nova, guarda blocos num &lt;code&gt;RamBlockstore&lt;/code&gt; e registros IPNS (InterPlanetary Name System) num &lt;code&gt;RamRecordStore&lt;/code&gt;. RAM (Random Access Memory) aqui quer dizer a memória do processo: nada vai para disco. O listener é TCP (Transmission Control Protocol), uma porta só.&lt;/p&gt;

&lt;p&gt;Sem &lt;code&gt;--public&lt;/code&gt;, o bind é &lt;code&gt;127.0.0.1&lt;/code&gt;, o endereço que só a própria máquina alcança, e a lista de bootstrap é a que você passou em &lt;code&gt;--bootstrap&lt;/code&gt;, muitas vezes vazia. Com &lt;code&gt;--public&lt;/code&gt;, o bind é &lt;code&gt;0.0.0.0&lt;/code&gt;, todas as interfaces, e o bootstrap são os nós públicos de &lt;code&gt;Config.defaultBootstrapNodes&lt;/code&gt;. Os dois modos se excluem: passar os dois lança erro. &lt;code&gt;--public&lt;/code&gt; também liga &lt;code&gt;provideBlocks&lt;/code&gt; só nos comandos que anunciam. O multiaddr impresso por &lt;code&gt;advertise()&lt;/code&gt; é o que o outro processo cola em &lt;code&gt;--bootstrap&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A ordem abaixo segue o que cada CLI pede da rede. Drop não usa DHT (Distributed Hash Table, a tabela de quem tem o CID): o outro processo ganha o multiaddr na saída e pede o bloco àquele par. Find anuncia o CID e o outro lado só precisa desse CID. Name move um nome IPNS. Chat manda HTTP (Hypertext Transfer Protocol) no stream &lt;code&gt;/http&lt;/code&gt;. ApiBlocks fala com um daemon já de pé, pela API (Application Programming Interface) do post anterior.&lt;/p&gt;

&lt;p&gt;Os scripts em &lt;code&gt;examples/scripts/&lt;/code&gt; sobem os dois processos e conferem a saída. No Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;powershell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Bypass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;examples/scripts/all.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Há um par &lt;code&gt;.ps1&lt;/code&gt; e &lt;code&gt;.sh&lt;/code&gt; por exemplo: &lt;code&gt;chat&lt;/code&gt;, &lt;code&gt;drop&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;api&lt;/code&gt;. Um script que passa imprime &lt;code&gt;PASS&lt;/code&gt; e o nome. &lt;code&gt;all.ps1&lt;/code&gt; e &lt;code&gt;all.sh&lt;/code&gt; imprimem &lt;code&gt;PASS all&lt;/code&gt; no fim. Antes disso, &lt;code&gt;mvn install&lt;/code&gt; na raiz publica o Nabu no Maven local, para o &lt;code&gt;examples/pom.xml&lt;/code&gt; compilar. O script da API ainda precisa do jar com dependências, &lt;code&gt;mvn package -DskipTests&lt;/code&gt;, porque ele sobe o processo &lt;code&gt;Nabu&lt;/code&gt; por esse jar. Os outros quatro chamam &lt;code&gt;mvn -f examples/pom.xml exec:java&lt;/code&gt;. O próprio script compila o módulo, então um ajuste no exemplo entra na execução seguinte sem um &lt;code&gt;compile&lt;/code&gt; à parte.&lt;/p&gt;

&lt;p&gt;Chat, Drop, Find e Name escutam uma porta efêmera, escolhida na hora. Não ocupam 4001 (swarm, as conexões entre pares), 5001 (API) nem 9100 (métricas). O script da API ocupa as três e disca os nós públicos de bootstrap, então a máquina precisa de rede até eles. Se a porta estiver tomada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;powershell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Bypass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;examples/scripts/all.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-SkipApi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No shell, o equivalente é &lt;code&gt;SKIP_API=1&lt;/code&gt;. A subida da API espera até três minutos pela linha &lt;code&gt;Starting RPC API server&lt;/code&gt;. RPC (Remote Procedure Call) aqui é só o nome dessa linha de log: o servidor HTTP da API subiu. O repositório do daemon é &lt;code&gt;target/example-tests/ipfs-repo&lt;/code&gt;, criado de novo a cada execução. Reusar um repositório travado deixa &lt;code&gt;node.start()&lt;/code&gt; parado em &lt;code&gt;Starting IPFS...&lt;/code&gt;. Os logs ficam em &lt;code&gt;target/example-tests/&lt;/code&gt;. Um script que falha imprime a saída do nó e mata os processos que ele mesmo abriu. &lt;code&gt;all&lt;/code&gt; tenta o script falho uma segunda vez e então para.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Drop&lt;/strong&gt; grava um bloco raw e para. Raw, no CID, é o codec dos bytes opacos: não há links dentro do bloco. &lt;code&gt;put&lt;/code&gt; chama &lt;code&gt;DemoNode.start(false, lista vazia, false)&lt;/code&gt;. O primeiro &lt;code&gt;false&lt;/code&gt; desliga o anúncio na DHT. A lista vazia é o bootstrap. O último &lt;code&gt;false&lt;/code&gt; prende o bind em &lt;code&gt;127.0.0.1&lt;/code&gt;. O bloco sai de &lt;code&gt;DemoNode.payload&lt;/code&gt;. Se sobrou um caminho, lê o arquivo inteiro. Senão imprime &lt;code&gt;Text to store:&lt;/code&gt; e usa uma linha da entrada. &lt;code&gt;blockstore.put&lt;/code&gt; usa &lt;code&gt;Cid.Codec.Raw&lt;/code&gt;. A saída traz &lt;code&gt;Listening:&lt;/code&gt;, depois &lt;code&gt;CID:&lt;/code&gt;, e a frase de que os mesmos bytes produzem esse CID. O outro processo cola o que vier em &lt;code&gt;Other process: get &amp;lt;multiaddr&amp;gt; &amp;lt;cid&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Drop put [file]
Drop get &amp;lt;multiaddr&amp;gt; &amp;lt;cid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;get&lt;/code&gt; sobe outro &lt;code&gt;DemoNode&lt;/code&gt; do mesmo jeito, chama &lt;code&gt;remember&lt;/code&gt; no multiaddr e &lt;code&gt;fetch&lt;/code&gt; com o conjunto daquele único PeerId, timeout de 30 segundos. &lt;code&gt;remember&lt;/code&gt; faz &lt;code&gt;addAddrs&lt;/code&gt; no caderno. &lt;code&gt;fetch&lt;/code&gt; chama &lt;code&gt;ipfs.getBlocks&lt;/code&gt; com &lt;code&gt;addToLocal&lt;/code&gt; verdadeiro. A linha final é &lt;code&gt;Fetched &amp;lt;cid&amp;gt; from &amp;lt;peerId&amp;gt;&lt;/code&gt;. A DHT não entra, porque a lista de pares não está vazia.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find&lt;/strong&gt; é o anúncio. &lt;code&gt;put&lt;/code&gt; sobe com &lt;code&gt;provideBlocks&lt;/code&gt; verdadeiro e, depois do &lt;code&gt;put&lt;/code&gt; raw, chama &lt;code&gt;dht.provideBlock&lt;/code&gt; com o CID e &lt;code&gt;PeerAddresses.fromHost&lt;/code&gt;, e espera o future. A frase na saída diz que o outro lado só precisa do CID, e pede para deixar o processo vivo: o provedor precisa continuar discável. &lt;code&gt;get&lt;/code&gt; sobe com &lt;code&gt;provideBlocks&lt;/code&gt; falso, lê o CID e chama &lt;code&gt;findProviders&lt;/code&gt; pedindo 5 provedores. Imprime cada PeerId e os endereços. &lt;code&gt;remember&lt;/code&gt; grava esses endereços no caderno, pulando provedor sem endereço. Em seguida &lt;code&gt;fetch&lt;/code&gt; vai com a lista de pares vazia. A busca do bloco passa de novo pela DHT, agora com os endereços já conhecidos, no caminho do &lt;code&gt;BitswapBlockService&lt;/code&gt; que tenta os conectados e depois os provedores. Sem registro, a linha é &lt;code&gt;No provider records.&lt;/code&gt; Com &lt;code&gt;--public&lt;/code&gt;, os dois lados usam a DHT pública. Sem ele, &lt;code&gt;get&lt;/code&gt; imprime &lt;code&gt;Start the other process with: put --bootstrap&lt;/code&gt; mais o multiaddr local, e os dois formam uma DHT de dois nós.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name&lt;/strong&gt; publica IPNS. O comentário da classe pede para subir &lt;code&gt;resolve&lt;/code&gt; primeiro e passar o multiaddr dele a &lt;code&gt;publish --bootstrap&lt;/code&gt;. &lt;code&gt;publish&lt;/code&gt; sobe com &lt;code&gt;provideBlocks&lt;/code&gt; verdadeiro. Cada linha da entrada vira um bloco raw. O valor do registro são os bytes UTF-8 do CID em texto, não os bytes do bloco. O sequence começa em 0 e incrementa antes de cada &lt;code&gt;publishValue&lt;/code&gt;, então o primeiro anúncio sai com sequence 1. O TTL (Time To Live, a vida que o registro declara) é 24 horas, a constante &lt;code&gt;TTL_HOURS&lt;/code&gt;. A saída de cada linha é &lt;code&gt;Publishing sequence ...&lt;/code&gt;, o CID e &lt;code&gt;IPNS record stored on N peers.&lt;/code&gt;, onde N é o inteiro que &lt;code&gt;publishValue&lt;/code&gt; devolve, a quantidade de pares que guardaram o registro. &lt;code&gt;resolve&lt;/code&gt; sobe sem anunciar, pede o PeerId do publicador e aceita &lt;code&gt;resolve&lt;/code&gt;, &lt;code&gt;get &amp;lt;cid&amp;gt;&lt;/code&gt; ou &lt;code&gt;quit&lt;/code&gt;. &lt;code&gt;resolve&lt;/code&gt; chama &lt;code&gt;resolveRecords&lt;/code&gt; com &lt;code&gt;minResults&lt;/code&gt; 1, imprime &lt;code&gt;IPNS sequence&lt;/code&gt; e o CID, e busca o bloco. Sem &lt;code&gt;--public&lt;/code&gt;, o conjunto de pares é só o publicador e o timeout é 30 segundos. Com &lt;code&gt;--public&lt;/code&gt;, o conjunto vai vazio, o timeout sobe para 120 segundos e a DHT entra. Sem registro, a linha é &lt;code&gt;No IPNS record yet.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chat&lt;/strong&gt; sobe com &lt;code&gt;provideBlocks&lt;/code&gt; falso e registra o handler &lt;code&gt;incoming&lt;/code&gt;. O primeiro prompt pede um PeerId ou um multiaddr, e &lt;code&gt;remember&lt;/code&gt; grava o endereço. Cada linha seguinte vira um &lt;code&gt;DefaultFullHttpRequest&lt;/code&gt; POST para &lt;code&gt;/&lt;/code&gt;, com o texto no corpo. &lt;code&gt;p2pHttp&lt;/code&gt; precisa estar presente. &lt;code&gt;dial&lt;/code&gt; usa o host, o PeerId e os multiaddrs, e &lt;code&gt;send&lt;/code&gt; espera a resposta. Falha de envio imprime &lt;code&gt;Could not send:&lt;/code&gt;. O handler remoto lê o corpo e imprime &lt;code&gt;received:&lt;/code&gt;. O script &lt;code&gt;chat.ps1&lt;/code&gt; sobe o primeiro sem argumentos e o segundo com &lt;code&gt;--bootstrap&lt;/code&gt; no endereço que o primeiro imprimiu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ApiBlocks&lt;/strong&gt; é cliente, não nó. O padrão é o multiaddr &lt;code&gt;/ip4/127.0.0.1/tcp/5001&lt;/code&gt;. Um argumento só, se começa com &lt;code&gt;/&lt;/code&gt;, substitui o multiaddr. Se não começa, é o arquivo a guardar. Dois argumentos são o multiaddr e o arquivo. Sem arquivo, o programa lê uma linha, &lt;code&gt;Text to store:&lt;/code&gt;. &lt;code&gt;NabuClient&lt;/code&gt; chama &lt;code&gt;id&lt;/code&gt;, imprime &lt;code&gt;PeerId:&lt;/code&gt;, faz &lt;code&gt;block/put&lt;/code&gt; em raw, imprime &lt;code&gt;CID:&lt;/code&gt;, faz &lt;code&gt;block/get&lt;/code&gt; e compara os bytes. Diferença lança &lt;code&gt;block/get returned different bytes&lt;/code&gt;. Sucesso imprime &lt;code&gt;block/get matches block/put&lt;/code&gt;. Depois chama &lt;code&gt;dht/findprovs&lt;/code&gt;. Sem provedor, a linha é &lt;code&gt;dht/findprovs: no providers&lt;/code&gt;. Com provedor, imprime PeerId e endereços.&lt;/p&gt;

&lt;p&gt;O que fica de fora: os exemplos não cobrem S3, filtro, UPnP (Universal Plug and Play) nem relay. O blockstore é RAM. Matar o processo apaga blocos e registros. O script da API é o único que fala com a rede pública de bootstrap. Os outros quatro ficam entre si.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/http-entre-pares-5g2f"&gt;HTTP entre pares&lt;/a&gt;. Este é o último post.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>HTTP entre pares</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:04:45 +0000</pubDate>
      <link>https://dev.to/pedropietro/http-entre-pares-5g2f</link>
      <guid>https://dev.to/pedropietro/http-entre-pares-5g2f</guid>
      <description>&lt;p&gt;Na série IPFS com o Nabu, a API (Application Programming Interface) HTTP do post anterior escuta na máquina local. Este post leva um pedido HTTP até outro par, sem que esse par tenha um endereço público na internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;Dois programas já sabem falar HTTP (Hypertext Transfer Protocol): um verbo, um caminho, cabeçalhos e um corpo. Um deles está atrás de NAT (Network Address Translation, a reescrita de endereços do roteador). Abrir uma porta no roteador, ou alugar um servidor para os dois se encontrarem, repete o problema que o libp2p já resolve nos posts de transporte e de alcance.&lt;/p&gt;

&lt;p&gt;A proposta é carregar o pedido e a resposta HTTP num stream libp2p. O encontro dos pares continua sendo por PeerId (o identificador do par). O conteúdo do stream continua sendo HTTP comum.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;O protocolo se chama &lt;code&gt;/http&lt;/code&gt;. Um lado disca o outro pelo PeerId, abre um stream e escreve um pedido HTTP completo: método, URL (Uniform Resource Locator) relativo, headers e corpo. O outro lado trata o pedido e devolve o status, os headers e o corpo. O tratamento pode ser a própria aplicação, ou um encaminhamento para um servidor HTTP comum em &lt;code&gt;localhost&lt;/code&gt;. Visto de fora, o par remoto parece um servidor. Visto da rede, é mais um protocolo no mesmo host que já fala Bitswap e Kademlia.&lt;/p&gt;

&lt;p&gt;Quem chama precisa do endereço do par. Se o caderno já tem multiaddrs, a discagem usa essa lista. Se não tem, a DHT (Distributed Hash Table) procura o par, como no post da Kademlia. Um proxy local traduz um caminho &lt;code&gt;/p2p/&amp;lt;peerid&amp;gt;/http/&amp;lt;caminho&amp;gt;&lt;/code&gt; nessa discagem. Um cliente HTTP comum, na mesma máquina, fala com &lt;code&gt;127.0.0.1&lt;/code&gt; e não precisa saber o que é um PeerId. O proxy corta o PeerId do caminho, acha o par e escreve o resto do caminho no pedido que segue pelo stream.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  client[localHttpClient] --&amp;gt; proxy[p2pProxy]
  proxy --&amp;gt; dht[addressOrDht]
  dht --&amp;gt; stream[httpStream]
  stream --&amp;gt; app[remoteHandlerOrLocalhost]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;HttpProtocol&lt;/code&gt;, em &lt;code&gt;src/main/java/org/peergos/protocol/http/HttpProtocol.java&lt;/code&gt;, registra o identificador &lt;code&gt;/http&lt;/code&gt; no &lt;code&gt;Binding&lt;/code&gt;. Há dois construtores de binding. Um recebe um &lt;code&gt;SocketAddress&lt;/code&gt; e encaminha cada pedido para esse host e porta. O outro recebe um &lt;code&gt;HttpRequestProcessor&lt;/code&gt;, a função da aplicação. O corpo máximo tratado de uma vez é &lt;code&gt;MAX_BODY_SIZE&lt;/code&gt;, 8 MiB. O comentário no código marca isso como limite temporário, no lugar de um fluxo contínuo. &lt;code&gt;Sender.send&lt;/code&gt; enfileira um future, escreve o &lt;code&gt;FullHttpRequest&lt;/code&gt; no stream e completa quando &lt;code&gt;onMessage&lt;/code&gt; copia status, headers e corpo para um &lt;code&gt;DefaultFullHttpResponse&lt;/code&gt;. O stream fecha em seguida. &lt;code&gt;onClosed&lt;/code&gt;, &lt;code&gt;onReadClosed&lt;/code&gt; e &lt;code&gt;onException&lt;/code&gt; falham os futures que ainda estavam na fila, com a mensagem de stream fechado ou a exceção original.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; só acrescenta o protocolo quando a aplicação passa um handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HttpProtocol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Binding&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;httpHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpProtocol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Binding&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;httpHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ifPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;protocols:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O campo &lt;code&gt;p2pHttp&lt;/code&gt; do &lt;code&gt;EmbeddedIpfs&lt;/code&gt; fica vazio sem isso. Bitswap e Kademlia seguem normais. Quem for discar mais tarde usa &lt;code&gt;ipfs.p2pHttp.get()&lt;/code&gt; e recebe vazio se o build não registrou o protocolo.&lt;/p&gt;

&lt;p&gt;No processo &lt;code&gt;Nabu&lt;/code&gt;, o handler existe quando o config tem &lt;code&gt;Addresses.ProxyTarget&lt;/code&gt;. &lt;code&gt;proxyHandler&lt;/code&gt; chama &lt;code&gt;HttpProtocol.proxyRequest&lt;/code&gt; para o host e a porta desse multiaddr, e passa o PeerId remoto. O mesmo processo publica &lt;code&gt;HttpProxyHandler&lt;/code&gt; em &lt;code&gt;/p2p/&lt;/code&gt;, a constante &lt;code&gt;HttpProxyService.API_URL&lt;/code&gt;. O comentário do handler descreve o caminho: &lt;code&gt;/p2p/$target_node_id/http/$target_path&lt;/code&gt;. O handler corta o prefixo &lt;code&gt;/p2p/&lt;/code&gt;, separa o PeerId do caminho que segue, e pede ao &lt;code&gt;HttpProxyService&lt;/code&gt; a discagem.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HttpProxyService.getAddresses&lt;/code&gt; olha o caderno do host. Se a coleção daquele PeerId não está vazia, devolve esses multiaddrs. Se está vazia, &lt;code&gt;lookupAddresses&lt;/code&gt; chama &lt;code&gt;dht.findClosestPeers&lt;/code&gt; pedindo um resultado. O comentário do método explica o segundo argumento &lt;code&gt;useCached&lt;/code&gt;: a DHT responde uma busca de par a partir do caderno quando pode, e isso não serve para quem está tentando escapar de um endereço morto. &lt;code&gt;useCached&lt;/code&gt; falso força a consulta. O resultado fica com o par cujo id coincide com o alvo. Endereço velho, de um par que mudou de rede, faria o proxy insistir num destino que não atende.&lt;/p&gt;

&lt;p&gt;O buffer de 128 MiB do yamux, do post de transportes, existe por causa daqui. O yamux limita o buffer por conexão. Uma resposta perto de &lt;code&gt;MAX_BODY_SIZE&lt;/code&gt; já ocupa uma fatia grande. Três respostas de 4 MB na mesma conexão estouravam o padrão de 10 MiB, e o muxer resetava os streams. &lt;code&gt;writeAndFlush&lt;/code&gt; não devolve future, então o reset aparecia só como resposta que não chegava.&lt;/p&gt;

&lt;p&gt;O exemplo mínimo de duas pessoas trocando mensagens por esse caminho é o &lt;a href="https://github.com/Peergos/nabu-chat" rel="noopener noreferrer"&gt;nabu-chat&lt;/a&gt;: os PeerIds se encontram fora da banda, a conexão é libp2p, e as mensagens são pedidos HTTP. O módulo &lt;code&gt;examples&lt;/code&gt; deste repositório tem um &lt;code&gt;Chat&lt;/code&gt; com a mesma ideia. Cada linha vira um &lt;code&gt;POST /&lt;/code&gt; no stream &lt;code&gt;/http&lt;/code&gt;, e o handler do outro lado imprime &lt;code&gt;received:&lt;/code&gt;. O próximo post o executa.&lt;/p&gt;

&lt;p&gt;O que fica de fora: &lt;code&gt;/http&lt;/code&gt; não é um gateway de conteúdo IPFS. Ele não resolve CID nem IPNS. Carrega HTTP opaco entre dois pares que já se encontram pelo libp2p. O corpo de 8 MiB é o teto desta implementação. Uma resposta maior precisa de outro desenho.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/a-api-http-e-como-rodar-o-nabu-3lbm"&gt;A API HTTP e como rodar o Nabu&lt;/a&gt;. O próximo é &lt;a href="https://dev.to/pedropietro/os-exemplos-do-repositorio-170o"&gt;Os exemplos do repositório&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>A API HTTP e como rodar o Nabu</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:04:10 +0000</pubDate>
      <link>https://dev.to/pedropietro/a-api-http-e-como-rodar-o-nabu-3lbm</link>
      <guid>https://dev.to/pedropietro/a-api-http-e-como-rodar-o-nabu-3lbm</guid>
      <description>&lt;p&gt;Na série IPFS com o Nabu, o nó já guarda blocos e fala com a rede. Este post é a porta de fora: o processo que sobe sozinho e a biblioteca que outra aplicação embute.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;Quem opera um nó quer o mesmo tipo de chamada que já usa com o Kubo, a implementação de referência do IPFS em Go: HTTP (Hypertext Transfer Protocol) local, um caminho &lt;code&gt;/api/v0/...&lt;/code&gt;, o CID (Content Identifier) na query. Quem escreve uma aplicação quer o contrário: nenhuma porta HTTP, o nó como objeto Java dentro do processo.&lt;/p&gt;

&lt;p&gt;A proposta do Nabu é oferecer os dois. A superfície HTTP é de propósito menor que a do Kubo. O objeto embutido expõe o mesmo nó que essa superfície usa por baixo.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;O modo standalone lê um diretório de repositório, no estilo do Kubo. A variável de ambiente ou o argumento &lt;code&gt;IPFS_PATH&lt;/code&gt; aponta para ele. Na ausência, o diretório é &lt;code&gt;~/.ipfs&lt;/code&gt;, debaixo do home do usuário. Lá estão o arquivo &lt;code&gt;config&lt;/code&gt; e os dados. O config é JSON (JavaScript Object Notation) com a identidade, os endereços e o datastore. O processo abre a API (Application Programming Interface) HTTP no host e na porta que o config chama de &lt;code&gt;Addresses.API&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Uma chamada típica é &lt;code&gt;POST /api/v0/block/get?arg=&amp;lt;cid&amp;gt;&lt;/code&gt;. O corpo da resposta são os bytes do bloco. &lt;code&gt;block/put&lt;/code&gt; manda os bytes num corpo multipart, o formato em que cada parte traz um arquivo, e recebe de volta um JSON com o campo &lt;code&gt;Hash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O modo embutido não abre essa porta. A aplicação chama &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; com as peças que escolhe: store de registros, blockstore, se deve anunciar blocos, endereços de swarm, bootstrap, identidade, authoriser e, se quiser, um handler HTTP entre pares. &lt;code&gt;start&lt;/code&gt; entra na rede. A partir daí &lt;code&gt;getBlocks&lt;/code&gt;, &lt;code&gt;publishValue&lt;/code&gt; e o &lt;code&gt;blockstore&lt;/code&gt; são chamadas locais, no mesmo processo.&lt;/p&gt;

&lt;p&gt;A API compatível com o Kubo cobre bloco, identidade, refs, um pedaço da DHT (Distributed Hash Table) e a leitura IPNS (InterPlanetary Name System). Caminhos de arquivo, &lt;code&gt;add&lt;/code&gt; de diretório e pin não estão nessa lista.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;A classe do processo é &lt;code&gt;Nabu&lt;/code&gt;, em &lt;code&gt;src/main/java/org/peergos/Nabu.java&lt;/code&gt;. &lt;code&gt;getIPFSPath&lt;/code&gt; lê o argumento &lt;code&gt;IPFS_PATH&lt;/code&gt; ou, sem ele, &lt;code&gt;HOME&lt;/code&gt; mais &lt;code&gt;.ipfs&lt;/code&gt;. &lt;code&gt;readConfig&lt;/code&gt; abre &lt;code&gt;config&lt;/code&gt; nesse diretório. Se o arquivo não existe, grava um config padrão. O argumento &lt;code&gt;s3.datastore&lt;/code&gt;, quando vem na linha de comando, preenche o mount S3 antes dessa gravação. O authoriser é &lt;code&gt;(c, p, a) -&amp;gt; CompletableFuture.completedFuture(true)&lt;/code&gt;: toda leitura de bloco é autorizada. O store de IPNS é um &lt;code&gt;DatabaseRecordStore&lt;/code&gt; em &lt;code&gt;datastore/h2-v2.datastore&lt;/code&gt;. O blockstore sai de &lt;code&gt;buildBlockStore&lt;/code&gt;. &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; recebe &lt;code&gt;provideBlocks&lt;/code&gt; verdadeiro, os endereços de swarm, o bootstrap e a identidade do config.&lt;/p&gt;

&lt;p&gt;Se &lt;code&gt;config.addresses.enableUPnP&lt;/code&gt; está ligado, &lt;code&gt;enablePortForwarding&lt;/code&gt; roda antes de &lt;code&gt;start&lt;/code&gt;. &lt;code&gt;start(false)&lt;/code&gt; faz o bootstrap de forma síncrona. O endereço da API é o argumento &lt;code&gt;Addresses.API&lt;/code&gt;, se foi passado, ou &lt;code&gt;config.addresses.apiAddress&lt;/code&gt;. O servidor é &lt;code&gt;com.sun.net.httpserver.HttpServer&lt;/code&gt;, com fila de 500 conexões e um pool de 50 threads. O contexto &lt;code&gt;/api/v0/&lt;/code&gt; recebe um &lt;code&gt;APIHandler&lt;/code&gt;. Se o config tem &lt;code&gt;Addresses.ProxyTarget&lt;/code&gt;, o HTTP entre pares encaminha para esse host e porta, e o servidor ganha o contexto &lt;code&gt;/p2p/&lt;/code&gt;. Um shutdown hook para o servidor HTTP com espera de 3 segundos. Métricas, se &lt;code&gt;config.metrics.enabled&lt;/code&gt;, sobem noutro endereço, o do &lt;code&gt;MetricsSection&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;APIHandler&lt;/code&gt; exige que o caminho comece com &lt;code&gt;/api/v0/&lt;/code&gt;. O resto entra num &lt;code&gt;switch&lt;/code&gt;. Os casos, em &lt;code&gt;src/main/java/org/peergos/net/APIHandler.java&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; devolve JSON com o PeerId em base58, no campo &lt;code&gt;ID&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;version&lt;/code&gt; devolve o campo &lt;code&gt;Version&lt;/code&gt;. A constante do handler é &lt;code&gt;0.9.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block/get&lt;/code&gt; exige um &lt;code&gt;arg&lt;/code&gt;. Monta um &lt;code&gt;Want&lt;/code&gt; com o CID e o &lt;code&gt;auth&lt;/code&gt; opcional. &lt;code&gt;peers&lt;/code&gt; vira um conjunto de PeerIds. &lt;code&gt;persist&lt;/code&gt; vem da query e, se falta, vale verdadeiro. A resposta são os bytes, ou status 400 se a lista volta vazia.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block/put&lt;/code&gt; exige &lt;code&gt;format&lt;/code&gt;. Lê um único arquivo do multipart, cujo boundary vem do header &lt;code&gt;Content-Type&lt;/code&gt;. Recusa tamanho acima de &lt;code&gt;maxBlockSize&lt;/code&gt;, o teto da mensagem Bitswap, 2 MiB. O codec sai de &lt;code&gt;Cid.Codec.lookupIPLDName&lt;/code&gt;. A resposta JSON traz &lt;code&gt;Hash&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block/rm&lt;/code&gt; apaga um CID. Sucesso devolve &lt;code&gt;Hash&lt;/code&gt; e &lt;code&gt;Error&lt;/code&gt; vazio. Falha responde 400.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block/rm/bulk&lt;/code&gt; lê uma lista &lt;code&gt;cids&lt;/code&gt; no corpo JSON.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block/stat&lt;/code&gt; busca o bloco com &lt;code&gt;persist&lt;/code&gt; falso e devolve só &lt;code&gt;Size&lt;/code&gt;. A lista de pares vai vazia. Falha responde 400.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block/stat/bulk&lt;/code&gt; lê &lt;code&gt;{"wants":[...]}&lt;/code&gt;, busca com &lt;code&gt;persist&lt;/code&gt; verdadeiro e, para cada bloco, devolve &lt;code&gt;size&lt;/code&gt;, os links que não são multihash do tipo &lt;code&gt;identity&lt;/code&gt;, e os BATs (Block Access Token, o segredo de leitura) encontrados no bloco. Responde 400 se o número de blocos não fecha com o de wants.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block/has&lt;/code&gt; responde os bytes &lt;code&gt;true&lt;/code&gt; ou &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;refs/local&lt;/code&gt; lista os CIDs de &lt;code&gt;blockstore.refs&lt;/code&gt;. O parâmetro &lt;code&gt;use-block-store&lt;/code&gt; vale falso se não vier. Cada CID sai num JSON com o campo &lt;code&gt;Ref&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bloom/add&lt;/code&gt; chama &lt;code&gt;blockstore.bloomAdd&lt;/code&gt; e responde o booleano.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dht/findprovs&lt;/code&gt; exige o CID. &lt;code&gt;num-providers&lt;/code&gt; positivo substitui o padrão 20. Cada provedor vira &lt;code&gt;ID&lt;/code&gt; e &lt;code&gt;Addrs&lt;/code&gt; dentro de &lt;code&gt;Responses&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ipns/get&lt;/code&gt; exige o argumento &lt;code&gt;signer&lt;/code&gt;, um PeerId em base58. Chama &lt;code&gt;resolveRecords&lt;/code&gt; pedindo pelo menos um resultado, ordena, e devolve &lt;code&gt;sig&lt;/code&gt; e &lt;code&gt;data&lt;/code&gt; em hexadecimal do registro de maior sequence. Lista vazia vira &lt;code&gt;Couldn't resolve&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;block/get&lt;/code&gt; com &lt;code&gt;peers&lt;/code&gt; vazio deixa o &lt;code&gt;BitswapBlockService&lt;/code&gt; tentar os conectados e depois a DHT. &lt;code&gt;persist&lt;/code&gt; falso busca sem gravar no store local.&lt;/p&gt;

&lt;p&gt;O cliente Java dessa API é &lt;code&gt;NabuClient&lt;/code&gt;, em &lt;code&gt;src/main/java/org/peergos/client/NabuClient.java&lt;/code&gt;. O construtor de host e porta assume &lt;code&gt;/api/v0/&lt;/code&gt;. O construtor de multiaddr, por exemplo &lt;code&gt;/ip4/127.0.0.1/tcp/5001&lt;/code&gt;, lê host e porta dali. Timeout de conexão: 10 segundos. Timeout de leitura: 60 segundos. Os exemplos do último post usam esse cliente contra um daemon.&lt;/p&gt;

&lt;p&gt;Embutir segue o trecho do README. A identidade pode ser gerada na hora:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;HostBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HostBuilder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;generateIdentity&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;PrivKey&lt;/span&gt; &lt;span class="n"&gt;privKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPrivateKey&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;PeerId&lt;/span&gt; &lt;span class="n"&gt;peerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPeerId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;IdentitySection&lt;/span&gt; &lt;span class="n"&gt;identity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IdentitySection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;privKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;peerId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; recebe o &lt;code&gt;RamRecordStore&lt;/code&gt; ou um store durável, o &lt;code&gt;FileBlockstore&lt;/code&gt;, a lista de swarm e de bootstrap, e o authoriser. &lt;code&gt;ipfs.start()&lt;/code&gt; bloqueia até o host escutar, o identify estar registrado e o bootstrap da tabela ter corrido. &lt;code&gt;getBlocks&lt;/code&gt; devolve &lt;code&gt;List&amp;lt;HashedBlock&amp;gt;&lt;/code&gt;. Cada item traz o CID e o &lt;code&gt;byte[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O que fica de fora: a API não replica o Kubo inteiro. Não há &lt;code&gt;files&lt;/code&gt;, &lt;code&gt;dag&lt;/code&gt;, &lt;code&gt;name/publish&lt;/code&gt; HTTP, nem gateway de conteúdo no estilo &lt;code&gt;ipfs.io&lt;/code&gt;. O campo &lt;code&gt;Addresses.Gateway&lt;/code&gt; existe no config e é lido por &lt;code&gt;AddressesSection&lt;/code&gt;. O handler que sobe é o da API, mais o proxy &lt;code&gt;/p2p/&lt;/code&gt; quando há alvo. O pacote Maven publicado pelo JitPack é &lt;code&gt;com.github.peergos:nabu&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/onde-o-bloco-mora-3e47"&gt;Onde o bloco mora&lt;/a&gt;. O próximo é &lt;a href="https://dev.to/pedropietro/http-entre-pares-5g2f"&gt;HTTP entre pares&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Onde o bloco mora</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:04:09 +0000</pubDate>
      <link>https://dev.to/pedropietro/onde-o-bloco-mora-3e47</link>
      <guid>https://dev.to/pedropietro/onde-o-bloco-mora-3e47</guid>
      <description>&lt;p&gt;Na série IPFS com o Nabu, o CID (Content Identifier, o identificador do conteúdo) já nomeia o bloco e o Bitswap já o transporta. Este post é o lugar onde os bytes ficam quando o processo guarda uma cópia.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;Um bloco chega e precisa sobreviver ao reinício. A pergunta "eu tenho este CID?" acontece o tempo todo: a cada want do Bitswap, o motor olha o store. Ir ao disco, ou a um serviço de objetos, para cada pergunta fica caro quando a resposta quase sempre é não. O store também precisa listar os CIDs que tem, para o anúncio periódico da DHT (Distributed Hash Table), e lembrar tamanho e links, para a API (Application Programming Interface) HTTP não abrir o bloco inteiro só para medir.&lt;/p&gt;

&lt;p&gt;A proposta é uma interface pequena, dois fundos intercambiáveis e camadas opcionais por cima: um filtro aproximado na frente e uma tabela de metadados ao lado.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;A interface é &lt;code&gt;has&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;put&lt;/code&gt;, &lt;code&gt;rm&lt;/code&gt;, &lt;code&gt;refs&lt;/code&gt;, &lt;code&gt;bloomAdd&lt;/code&gt;. A chave de arquivo deriva do multihash em base32, o alfabeto de 32 símbolos, sem o padding &lt;code&gt;=&lt;/code&gt;. O diretório reparte os arquivos para uma única pasta não guardar milhões de entradas.&lt;/p&gt;

&lt;p&gt;Dois fundos implementam o contrato. O de arquivo escreve cada bloco num &lt;code&gt;.data&lt;/code&gt; sob &lt;code&gt;blocks/&lt;/code&gt;. O de S3 (Simple Storage Service, o armazenamento de objetos no estilo da AWS) usa o mesmo contrato contra um bucket: uma região, um endpoint, um prefixo dentro do bucket. As credenciais podem vir do JSON (JavaScript Object Notation) de config, das variáveis &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; e &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;, ou de &lt;code&gt;~/.aws/credentials&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Por cima do fundo entra um filtro. Ele responde "não tenho" sem tocar o fundo quando o CID está fora de um conjunto aproximado. Um bloom filter é uma tabela de bits. Cada CID liga alguns bits. Se algum bit que o CID exigiria está apagado, o CID com certeza não foi inserido. Se todos estão acesos, o CID talvez esteja: outro CID pode ter acendido os mesmos bits. Essa coincidência é o falso positivo, e a taxa é configurável. O InfiniFilter é um quotient filter que cresce. Em vez de ser recriado do zero quando enche, aumenta a estrutura e alonga a impressão digital (fingerprint) de cada chave. Os dois podem dizer que um CID talvez esteja presente. Só o fundo confirma. Um segundo filtro pequeno guarda ausências recentes, para não repetir a ida ao disco logo depois de um &lt;code&gt;has&lt;/code&gt; negativo.&lt;/p&gt;

&lt;p&gt;Os metadados ficam à parte, numa tabela H2 aberta por JDBC (Java Database Connectivity, a API Java de bancos relacionais): tamanho, links, o que &lt;code&gt;block/stat&lt;/code&gt; precisa. O bloco em si continua no fundo.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;EmbeddedIpfs.buildBlockStore&lt;/code&gt; escolhe o fundo pelo prefixo do config, em &lt;code&gt;src/main/java/org/peergos/EmbeddedIpfs.java&lt;/code&gt;. &lt;code&gt;flatfs.datastore&lt;/code&gt; monta um &lt;code&gt;FileBlockstore&lt;/code&gt; no diretório IPFS e o embrulha num &lt;code&gt;CachingBlockMetadataStore&lt;/code&gt; junto com o metadata JDBC. Se &lt;code&gt;updateMetadb&lt;/code&gt; é verdadeiro, &lt;code&gt;updateMetadataStoreIfEmpty&lt;/code&gt; percorre os blocos quando a tabela ainda está vazia. &lt;code&gt;s3.datastore&lt;/code&gt; faz o análogo com &lt;code&gt;S3Blockstore&lt;/code&gt;, lendo região, bucket, &lt;code&gt;rootDirectory&lt;/code&gt; e &lt;code&gt;regionEndpoint&lt;/code&gt; dos parâmetros do mount. Outro prefixo lança &lt;code&gt;Unrecognized datastore prefix&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O caminho de arquivo está em &lt;code&gt;FileBlockstore.getFilePath&lt;/code&gt;. &lt;code&gt;hashToKey&lt;/code&gt; codifica o multihash em base32 e corta no primeiro &lt;code&gt;=&lt;/code&gt;. A pasta são os dois caracteres imediatamente anteriores ao último. O arquivo é a chave mais &lt;code&gt;.data&lt;/code&gt;, e a raiz é o diretório &lt;code&gt;blocks/&lt;/code&gt; criado no construtor se ainda não existe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashToKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;substring&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;()-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;BLOCK_FILE_SUFFIX&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;put&lt;/code&gt; grava com &lt;code&gt;TRUNCATE_EXISTING&lt;/code&gt; e &lt;code&gt;CREATE&lt;/code&gt;, e devolve o CID versão 1 com SHA-256 (Secure Hash Algorithm de 256 bits), como no primeiro post. &lt;code&gt;has&lt;/code&gt; é &lt;code&gt;file.exists()&lt;/code&gt;. &lt;code&gt;hasAny&lt;/code&gt; procura o mesmo multihash sob os codecs &lt;code&gt;DagCbor&lt;/code&gt;, &lt;code&gt;Raw&lt;/code&gt; e &lt;code&gt;DagProtobuf&lt;/code&gt;, montando um CID versão 1 para cada um. &lt;code&gt;get&lt;/code&gt; lê o arquivo inteiro para um &lt;code&gt;byte[]&lt;/code&gt;, ou devolve vazio se o arquivo não está.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;filteredBlockStore&lt;/code&gt; embrulha o fundo se &lt;code&gt;config.datastore.filter.type&lt;/code&gt; for &lt;code&gt;BLOOM&lt;/code&gt; ou &lt;code&gt;INFINI&lt;/code&gt;, com a taxa de falso positivo do config. &lt;code&gt;NONE&lt;/code&gt; devolve o fundo direto. Outro tipo é erro. &lt;code&gt;FilteredBlockstore.has&lt;/code&gt; só chama o fundo quando o filtro de presentes responde sim e o de ausências responde não. Se o fundo diz não, o CID entra no filtro de ausências. Esse filtro recomeça depois de 10.000 ausências, com um &lt;code&gt;CidInfiniFilter&lt;/code&gt; de 1.000 entradas e taxa 0,001. &lt;code&gt;get&lt;/code&gt; segue a mesma porta: fora do filtro de presentes, devolve vazio sem ler o fundo. &lt;code&gt;put&lt;/code&gt; grava no fundo e só então chama &lt;code&gt;present.add&lt;/code&gt;. &lt;code&gt;rm&lt;/code&gt; apaga no fundo e não mexe no filtro de presentes. Um CID removido pode continuar "talvez presente" até o filtro ser refeito. O &lt;code&gt;has&lt;/code&gt; seguinte vai ao fundo, recebe não, e registra a ausência.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bloomAdd&lt;/code&gt; insere o CID no filtro de presentes, sem gravar bytes. É o que a API &lt;code&gt;bloom/add&lt;/code&gt; faz. Sem isso, um CID que já está no fundo e ficou de fora do filtro seria invisível: &lt;code&gt;has&lt;/code&gt; e &lt;code&gt;get&lt;/code&gt; nem consultariam o disco. Se o fundo não tem o bloco, &lt;code&gt;has&lt;/code&gt; responde falso e o CID entra no filtro de ausências. O filtro sozinho não afirma a posse.&lt;/p&gt;

&lt;p&gt;O metadata nasce em &lt;code&gt;buildBlockMetadata&lt;/code&gt;. A URL JDBC é &lt;code&gt;jdbc:h2:&lt;/code&gt; mais o caminho de &lt;code&gt;nabu-blockmetadata.sql&lt;/code&gt;, com &lt;code&gt;MODE=PostgreSQL&lt;/code&gt;. A conexão fica embrulhada em &lt;code&gt;UncloseableConnection&lt;/code&gt; para um close acidental não derrubar o banco, e o auto-commit fica ligado. &lt;code&gt;TypeLimitedBlockstore&lt;/code&gt; entra por último, quando &lt;code&gt;allowedCodecs&lt;/code&gt; não está vazio. &lt;code&gt;put&lt;/code&gt; e &lt;code&gt;rm&lt;/code&gt; de um codec fora da lista lançam &lt;code&gt;Unsupported codec&lt;/code&gt;. &lt;code&gt;get&lt;/code&gt; de um codec fora da lista devolve vazio, sem chamar o fundo.&lt;/p&gt;

&lt;p&gt;Os exemplos usam &lt;code&gt;RamBlockstore&lt;/code&gt;: um mapa na memória do processo, sem arquivo, sem S3 e sem filtro. O processo &lt;code&gt;Nabu&lt;/code&gt; usa arquivo ou S3.&lt;/p&gt;

&lt;p&gt;O que fica de fora: não há Badger, LevelDB nem outro sharding além da pasta de dois caracteres. O filtro pode afirmar presença à toa, dentro da taxa configurada. A ausência confirmada pelo fundo é a resposta que vale. O InfiniFilter do código segue o artigo de Pagh, cresce por gerações de fingerprint, e a série não reproduz a matemática. O ponto prático é que &lt;code&gt;has&lt;/code&gt; barato continua correto o bastante para não ir ao S3 em todo want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/ipns-um-nome-que-muda-2k0n"&gt;IPNS, um nome que muda&lt;/a&gt;. O próximo é &lt;a href="https://dev.to/pedropietro/a-api-http-e-como-rodar-o-nabu-3lbm"&gt;A API HTTP e como rodar o Nabu&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>IPNS, um nome que muda</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:03:34 +0000</pubDate>
      <link>https://dev.to/pedropietro/ipns-um-nome-que-muda-2k0n</link>
      <guid>https://dev.to/pedropietro/ipns-um-nome-que-muda-2k0n</guid>
      <description>&lt;p&gt;Na série IPFS com o Nabu, o CID (Content Identifier) é permanente: os mesmos bytes, o mesmo nome. Este post é o nome que pode passar a apontar para outro CID.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;Um site, um perfil ou a cabeça de um feed mudam. Publicar de novo gera outro CID. Quem já guardou o CID antigo continua no conteúdo antigo, que é o combinado do endereçamento por conteúdo. Falta um nome estável, controlado por alguém, que diga "a versão atual é este CID".&lt;/p&gt;

&lt;p&gt;A proposta do IPNS (InterPlanetary Name System, o sistema de nomes interplanetário) é esse ponteiro. O nome não muda. O valor muda, e a mudança é assinada por quem tem a chave.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;A chave do registro é o PeerId de quem publica, prefixada pelos bytes de &lt;code&gt;/ipns/&lt;/code&gt;. O valor é um registro assinado pela chave privada correspondente. Quem quiser verifica a assinatura sem perguntar a um servidor. O registro traz o valor (em geral o caminho ou o CID atual, em bytes), um sequence, um prazo de validade e um TTL (Time To Live, por quanto tempo uma cópia pode ser reutilizada sem perguntar de novo).&lt;/p&gt;

&lt;p&gt;O sequence é um contador. &lt;code&gt;IpnsRecord.compareTo&lt;/code&gt; coloca o sequence maior depois do menor. No empate, o registro cuja validade acaba antes vem primeiro. Se a validade deste não acaba antes da do outro, a comparação devolve zero. &lt;code&gt;resolveValue&lt;/code&gt; ordena a lista e fica com o último, portanto com o sequence mais alto. Um registro vencido deixa de ser a resposta na validação do prazo. Como o CID do conteúdo continua imutável, a história antiga segue endereçável para quem guardou o CID. O IPNS só move o ponteiro.&lt;/p&gt;

&lt;p&gt;A publicação usa a mesma DHT (Distributed Hash Table) do post anterior. O registro é gravado nos pares mais próximos da chave &lt;code&gt;/ipns/&lt;/code&gt; mais o PeerId. Resolver é perguntar a esses pares e ficar com o registro de maior sequence que ainda valida.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IPNS.getKey&lt;/code&gt; monta a chave: escreve os bytes de &lt;code&gt;/ipns/&lt;/code&gt; e em seguida os bytes do multihash do publicador. &lt;code&gt;createSignedRecord&lt;/code&gt;, em &lt;code&gt;src/main/java/org/peergos/protocol/ipns/IPNS.java&lt;/code&gt;, monta o protobuf &lt;code&gt;IpnsEntry&lt;/code&gt;. O corpo assinado é um mapa CBOR (Concise Binary Object Representation) com as chaves &lt;code&gt;TTL&lt;/code&gt;, &lt;code&gt;Value&lt;/code&gt;, &lt;code&gt;Sequence&lt;/code&gt;, &lt;code&gt;Validity&lt;/code&gt; e &lt;code&gt;ValidityType&lt;/code&gt;. A validade é uma data no formato RFC 3339 com nanossegundos, em UTC, produzida por &lt;code&gt;formatExpiry&lt;/code&gt;. A assinatura cobre o prefixo ASCII &lt;code&gt;ipns-signature:&lt;/code&gt; mais esses bytes CBOR, e vai no campo &lt;code&gt;SignatureV2&lt;/code&gt;. Para Ed25519 a chave pública já está no PeerId, e o registro não repete &lt;code&gt;pubKey&lt;/code&gt;. Para outros tipos, o registro inclui a pública em protobuf. Dado extra da aplicação entra no mapa com o prefixo &lt;code&gt;_&lt;/code&gt; mais o sufixo que o chamador passou. O tamanho máximo aceito na validação é &lt;code&gt;MAX_RECORD_SIZE&lt;/code&gt;, 10 KiB. Entrada maior, ou sem &lt;code&gt;SignatureV2&lt;/code&gt; e sem &lt;code&gt;Data&lt;/code&gt;, é descartada por &lt;code&gt;parseAndValidateIpnsEntry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EmbeddedIpfs.publishValue&lt;/code&gt; é o atalho da aplicação. O publicador é o multihash do PeerId derivado da chave privada. O prazo é &lt;code&gt;LocalDateTime.now()&lt;/code&gt; mais &lt;code&gt;hoursTtl&lt;/code&gt;. O TTL em nanossegundos é &lt;code&gt;hoursTtl&lt;/code&gt; vezes 3.600.000.000.000. O método assina e chama a DHT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;signedRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;IPNS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createSignedRecord&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttlNanos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extraDataKeySuffix&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extraData&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;priv&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publishValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pub&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signedRecord&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O inteiro devolvido é quantos pares guardaram o registro. Há também &lt;code&gt;publishPresignedRecord&lt;/code&gt;, para quem já traz os bytes assinados. &lt;code&gt;resolveValue&lt;/code&gt; pede &lt;code&gt;resolveRecords&lt;/code&gt; e devolve o &lt;code&gt;value&lt;/code&gt; do último item depois de &lt;code&gt;sorted()&lt;/code&gt;. &lt;code&gt;IpnsRecord.compareTo&lt;/code&gt; ordena pelo sequence. O último é o mais novo. Lista vazia vira o erro &lt;code&gt;Couldn't resolve IPNS value&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Kademlia.publishValue&lt;/code&gt; valida antes de espalhar. &lt;code&gt;parseAndValidateIpnsEntry&lt;/code&gt; precisa devolver um registro, e o publicador desse registro precisa ser a chave pedida. Se já existe um registro local e o novo não é maior na comparação, o antigo permanece. A busca mira pelo menos 30 pares próximos da chave (&lt;code&gt;minPublishes&lt;/code&gt;), com 3 consultas em paralelo (&lt;code&gt;queryParallelism&lt;/code&gt;). A chave de roteamento é o SHA-256 dos bytes &lt;code&gt;/ipns/&lt;/code&gt; mais o publicador, não o PeerId cru.&lt;/p&gt;

&lt;p&gt;No processo standalone o store é &lt;code&gt;DatabaseRecordStore&lt;/code&gt;, H2, no caminho &lt;code&gt;datastore/h2-v2.datastore&lt;/code&gt; dentro do diretório IPFS. A tabela se chama &lt;code&gt;records&lt;/code&gt;. O valor cabe em 10 KiB (&lt;code&gt;SIZE_OF_VAL&lt;/code&gt;), o mesmo teto do registro, e o PeerId em 100 bytes na coluna. &lt;code&gt;RamRecordStore&lt;/code&gt; existe para testes e para os exemplos, que não precisam do registro depois que o processo morre. A interface é &lt;code&gt;RecordStore&lt;/code&gt;: ler, gravar e fechar. &lt;code&gt;EmbeddedIpfs.stop&lt;/code&gt; fecha o store.&lt;/p&gt;

&lt;p&gt;O que fica de fora: o Nabu não cria uma chave IPNS separada do PeerId por conta própria. A chave de publicação é a chave que a aplicação passa, e no fluxo de &lt;code&gt;publishValue&lt;/code&gt; ela é a identidade cujo PeerId entra na chave &lt;code&gt;/ipns/&lt;/code&gt;. Não há resolução IPNS por DNS. Um nome &lt;code&gt;ipns://&lt;/code&gt; no navegador fica com o cliente. A API HTTP expõe &lt;code&gt;ipns/get&lt;/code&gt;, descrito no post da API, e não expõe publicação.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/a-dht-kademlia-32ej"&gt;A DHT Kademlia&lt;/a&gt;. O próximo é &lt;a href="https://dev.to/pedropietro/onde-o-bloco-mora-3e47"&gt;Onde o bloco mora&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>A DHT Kademlia</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:03:32 +0000</pubDate>
      <link>https://dev.to/pedropietro/a-dht-kademlia-32ej</link>
      <guid>https://dev.to/pedropietro/a-dht-kademlia-32ej</guid>
      <description>&lt;p&gt;Na série IPFS com o Nabu, o Bitswap já sabe pedir um bloco a um par conhecido. Este post é como achar esse par quando ninguém passou o PeerId (o identificador do par).&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;Não existe um servidor central com a lista "quem tem este CID" (Content Identifier, o identificador do conteúdo). Cada nó só conhece uma fatia dos outros. A proposta da DHT (Distributed Hash Table, tabela de hash distribuída) é uma resposta que caiba nessa fatia e, ainda assim, chegue perto de quem publicou o anúncio.&lt;/p&gt;

&lt;p&gt;Kademlia é o formato dessa tabela no IPFS. A mesma estrutura serve para achar um par a partir do PeerId e para guardar os registros IPNS (InterPlanetary Name System) do próximo post.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;Kademlia organiza os pares por distância XOR (exclusive or, o ou exclusivo bit a bit). A chave de um par é o hash do PeerId, 256 bits. A distância entre duas chaves é o XOR dos bits. Quanto mais cedo o primeiro bit diferente, mais distantes. Duas chaves que só diferem no último bit são vizinhas. Duas que diferem no primeiro bit estão em metades opostas do espaço.&lt;/p&gt;

&lt;p&gt;Cada nó guarda uma tabela de roteamento: k-buckets, baldes de pares, um balde para cada faixa de distância. O nó conhece muita gente perto de si e poucos representantes de cada região longe. Com isso, qualquer chave é alcançável em poucos saltos. Cada salto pergunta a alguém mais próximo da chave do que o nó atual.&lt;/p&gt;

&lt;p&gt;Publicar um provedor é escrever, nos nós mais próximos da chave do CID, o registro "este PeerId tem este CID, nestes endereços". Procurar é perguntar, de balde em balde, pelos nós mais próximos dessa mesma chave, até alguém devolver o registro. O anúncio expira. Quem continua com o bloco anuncia de novo. Quem acabou de gravar um bloco anuncia logo, sem esperar o ciclo.&lt;/p&gt;

&lt;p&gt;O protocolo na rede pública é &lt;code&gt;/ipfs/kad/1.0.0&lt;/code&gt;. Existe um identificador de DHT local, &lt;code&gt;/ipfs/lan/kad/1.0.0&lt;/code&gt;, para uma tabela que não sai da rede de casa. O nó que o &lt;code&gt;EmbeddedIpfs&lt;/code&gt; monta fala a rede pública.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Kademlia&lt;/code&gt; em &lt;code&gt;src/main/java/org/peergos/protocol/dht/Kademlia.java&lt;/code&gt; escolhe o identificador no construtor. &lt;code&gt;localOnly&lt;/code&gt; verdadeiro seleciona &lt;code&gt;LAN_DHT_ID&lt;/code&gt;. Falso seleciona &lt;code&gt;WAN_DHT_ID&lt;/code&gt;, a constante &lt;code&gt;/ipfs/kad/1.0.0&lt;/code&gt;. &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; passa &lt;code&gt;false&lt;/code&gt;, e também as flags de QUIC e TCP calculadas a partir dos endereços de escuta. &lt;code&gt;setAddressBook&lt;/code&gt; repassa o caderno ao &lt;code&gt;KademliaEngine&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A tabela mora em &lt;code&gt;KademliaEngine&lt;/code&gt;. O roteador é criado com o hash de 256 bits do PeerId deste nó:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ourPeerId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bareMultihash&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toBytes&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O construtor de &lt;code&gt;Router&lt;/code&gt; documenta os três inteiros: &lt;code&gt;branchesPerLevel&lt;/code&gt; (quantos ramos nascem quando um k-bucket se divide), &lt;code&gt;maxNodesPerBucket&lt;/code&gt; (quantos pares cabem no balde) e &lt;code&gt;maxCacheNodesPerBucket&lt;/code&gt; (quantos cabem na cache do balde). Aqui os três valem 2. O identificador base tem 256 bits, e 2 divide 256, que é a condição que o construtor exige para a árvore fechar no último nível.&lt;/p&gt;

&lt;p&gt;Os provedores ficam num &lt;code&gt;RamProviderStore&lt;/code&gt; de 10.000 entradas, criado em &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; e entregue ao engine junto com o &lt;code&gt;RecordStore&lt;/code&gt; de IPNS e o &lt;code&gt;Optional&lt;/code&gt; do blockstore. Reiniciar esquece quem anunciou o quê para este nó. &lt;code&gt;getProviders&lt;/code&gt; lê dessa memória. Os registros IPNS usam o outro store, o do post seguinte.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bootstrapRoutingTable&lt;/code&gt; resolve &lt;code&gt;dnsaddr&lt;/code&gt; (o multiaddr que ainda é um nome DNS), grava os endereços no caderno e disca, com 5 segundos de timeout por par. &lt;code&gt;bootstrap&lt;/code&gt; então preenche a tabela a partir desses vizinhos. &lt;code&gt;BOOTSTRAP_PERIOD_MILLIS&lt;/code&gt; vale 300.000, e a thread de &lt;code&gt;startBootstrapThread&lt;/code&gt; repete o ciclo a cada esse intervalo.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;findProviders(Multihash block, Host us, int desiredCount)&lt;/code&gt; é a busca. &lt;code&gt;BitswapBlockService&lt;/code&gt; só a chama quando a aplicação não passou PeerIds. Nesse caso o serviço primeiro pergunta aos pares já conectados, com espera de 10 segundos. O que faltar segue para &lt;code&gt;findProviders&lt;/code&gt; do primeiro CID da lista, pedindo até 5 provedores, e o Bitswap busca o restante nesses pares. Se a aplicação passou PeerIds, a DHT não entra. É o caminho mais rápido que o README descreve. &lt;code&gt;EmbeddedIpfs.getBlocks&lt;/code&gt; recebe o conjunto e o repassa ao &lt;code&gt;BlockService&lt;/code&gt;. A API &lt;code&gt;dht/findprovs&lt;/code&gt; chama o mesmo método com &lt;code&gt;num-providers&lt;/code&gt;, padrão 20.&lt;/p&gt;

&lt;p&gt;Quem deve anunciar passa por &lt;code&gt;ProvidingBlockstore&lt;/code&gt;. Se &lt;code&gt;provideBlocks&lt;/code&gt; é verdadeiro, &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; embrulha o blockstore. O &lt;code&gt;put&lt;/code&gt; grava no fundo e, no &lt;code&gt;thenApply&lt;/code&gt;, coloca o CID em &lt;code&gt;toPublish&lt;/code&gt;, uma &lt;code&gt;LinkedBlockingDeque&lt;/code&gt;. O construtor de &lt;code&gt;EmbeddedIpfs&lt;/code&gt; cria o &lt;code&gt;PeriodicBlockProvider&lt;/code&gt; com essa fila, o intervalo &lt;code&gt;22 * 3600_000&lt;/code&gt; milissegundos (22 horas) e um fornecedor que chama &lt;code&gt;blockstore.refs(false)&lt;/code&gt;. &lt;code&gt;start&lt;/code&gt; liga duas threads, &lt;code&gt;CidReprovider&lt;/code&gt; e &lt;code&gt;NewCidProvider&lt;/code&gt;. A primeira percorre os CIDs já guardados e republica. A segunda publica o que acabou de entrar na fila. As duas só existem quando &lt;code&gt;provideBlocks&lt;/code&gt; é verdadeiro.&lt;/p&gt;

&lt;p&gt;Na conexão, o handler de identify do &lt;code&gt;HostBuilder&lt;/code&gt; abre um stream Kademlia com um par novo que anuncia &lt;code&gt;/ipfs/kad/1.0.0&lt;/code&gt;. A condição é &lt;code&gt;newPeer&lt;/code&gt;: o caderno estava vazio antes do identify. Assim a tabela cresce também com quem chega, não só com o bootstrap.&lt;/p&gt;

&lt;p&gt;O que fica de fora: a DHT do Nabu não é um banco de blocos. Ela guarda ponteiros para pares e registros IPNS. Os bytes continuam no Bitswap. O store de provedores é memória. Não há persistência de provider records entre reinícios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/bitswap-o-pedido-de-um-bloco-14db"&gt;Bitswap, o pedido de um bloco&lt;/a&gt;. O próximo é &lt;a href="https://dev.to/pedropietro/ipns-um-nome-que-muda-2k0n"&gt;IPNS, um nome que muda&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Bitswap, o pedido de um bloco</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:02:58 +0000</pubDate>
      <link>https://dev.to/pedropietro/bitswap-o-pedido-de-um-bloco-14db</link>
      <guid>https://dev.to/pedropietro/bitswap-o-pedido-de-um-bloco-14db</guid>
      <description>&lt;p&gt;Na série IPFS com o Nabu, o nó já está na rede. Este post é a troca do bloco: como um CID (Content Identifier, o identificador do conteúdo) vira bytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;Saber o CID não entrega os bytes. Algum par os tem, talvez vários. Pedir o arquivo inteiro a um desconhecido é frágil: a conexão cai no meio, e o que já chegou não tem nome próprio. A proposta do IPFS é pedir bloco a bloco. Cada bloco cabe numa mensagem e se verifica sozinho, porque o CID é o hash.&lt;/p&gt;

&lt;p&gt;O protocolo que carrega esse pedido se chama Bitswap. Os dois lados usam a mesma mensagem. Enquanto este nó pede, pode atender o pedido de quem está do outro lado.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;A mensagem carrega três coisas. Um want é o CID que este nó quer, com um segredo opcional. Um have, ou a ausência dele (dont-have), diz se o outro lado tem esse CID. Um block são os bytes, junto com um prefixo do CID para o receptor conferir.&lt;/p&gt;

&lt;p&gt;O want pode ir a pares que a aplicação já nomeou. Se a lista vem vazia, o serviço pergunta primeiro a quem já está conectado e, para o que ninguém entregou em 10 segundos, pede provedores à DHT (Distributed Hash Table, a tabela distribuída de "quem tem este CID"). O post da Kademlia é essa descoberta. O Bitswap é o transporte dos bytes.&lt;/p&gt;

&lt;p&gt;Há um gancho de autorização. Antes de colocar os bytes na resposta, o nó pergunta a um &lt;code&gt;BlockRequestAuthoriser&lt;/code&gt; se aquele par pode ler aquele CID. A resposta negativa vira um dont-have, mesmo com o bloco no disco. O Peergos usa isso com um BAT (Block Access Token), um segredo de 32 bytes que viaja no want. O processo Nabu standalone autoriza todo mundo.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  want[wantCid] --&amp;gt; local[localBlockstore]
  local --&amp;gt;|hit| done[hashedBlock]
  local --&amp;gt;|miss| peers[knownPeersOrDht]
  peers --&amp;gt; bitswap[bitswapWant]
  bitswap --&amp;gt; auth[authoriser]
  auth --&amp;gt; done&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;O identificador está em &lt;code&gt;Bitswap&lt;/code&gt;, em &lt;code&gt;src/main/java/org/peergos/protocol/bitswap/Bitswap.java&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;MAX_MESSAGE_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;PROTOCOL_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/ipfs/bitswap/1.2.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2 MiB é o teto de uma mensagem. O construtor que &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; usa aceita outro identificador e outro teto. O padrão é esse protocolo e &lt;code&gt;Bitswap.MAX_MESSAGE_SIZE&lt;/code&gt;. O quarto argumento de &lt;code&gt;BitswapEngine&lt;/code&gt;, &lt;code&gt;blockAggressivePeers&lt;/code&gt;, vai &lt;code&gt;false&lt;/code&gt; nesse build. Com ele verdadeiro, &lt;code&gt;allowConnection&lt;/code&gt; recusa PeerIds que o motor já marcou em &lt;code&gt;blockedPeers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EmbeddedIpfs.getBlocks&lt;/code&gt; separa o trabalho antes do protocolo. Para cada &lt;code&gt;Want&lt;/code&gt;, &lt;code&gt;blockstore.has&lt;/code&gt; decide a lista local ou a remota. A local é lida com &lt;code&gt;get&lt;/code&gt;. Falha ou ausência manda o want de volta para a lista remota. A lista remota segue para &lt;code&gt;BitswapBlockService.get&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Bitswap.get&lt;/code&gt; percorre os wants e pula CID cujo multihash é do tipo &lt;code&gt;identity&lt;/code&gt;: nesse tipo o "hash" já são os bytes, e não há bloco a transferir. Os demais entram em &lt;code&gt;engine.getWant&lt;/code&gt;, que devolve o future já existente se o mesmo want está em voo, ou cria um &lt;code&gt;WantResult&lt;/code&gt; novo. &lt;code&gt;sendWants&lt;/code&gt; empurra os pedidos. Um &lt;code&gt;DownloadManager&lt;/code&gt; por conjunto de pares, guardado numa cache de 100 entradas, mantém o envio vivo.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BitswapBlockService&lt;/code&gt; é quem decide a quem pedir. Se a aplicação passou PeerIds, a chamada é direta: &lt;code&gt;bitswap.get(hashes, us, peers, addToBlockstore)&lt;/code&gt; e cada future é esperado com &lt;code&gt;join&lt;/code&gt;. Se a lista vem vazia, o serviço usa &lt;code&gt;bitswap.getBroadcastAudience()&lt;/code&gt;, os pares de conexões que este nó iniciou, com espera de 10 segundos por future. Want que completou sai da lista. O que falta chama &lt;code&gt;dht.findProviders&lt;/code&gt; só com o primeiro CID, pedindo 5 provedores, e o Bitswap busca o restante nesses PeerIds.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BitswapEngine&lt;/code&gt; guarda os wants locais num mapa e, ao receber uma mensagem, decide o que devolver. O trecho que importa está em &lt;code&gt;src/main/java/org/peergos/protocol/bitswap/BitswapEngine.java&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blockPresent&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;authoriser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allowRead&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sourcePeerId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;MessageOuterClass&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Block&lt;/span&gt; &lt;span class="n"&gt;blockP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageOuterClass&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPrefix&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ByteString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyFrom&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefixBytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAuth&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ByteString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyFrom&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ArrayOps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hexToBytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;))))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ByteString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyFrom&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;store.has&lt;/code&gt; vem antes. Se o bloco existe e o authoriser aceita, os bytes entram na mensagem, com o prefixo do CID e o auth em bytes. Se a soma passa de &lt;code&gt;maxMessageSize&lt;/code&gt;, o motor descarrega o que já juntou e recomeça a mensagem. Se o bloco existe e o authoriser recusa, o want vai para &lt;code&gt;deniedWants&lt;/code&gt; (uma cache de 10.000) e a resposta é dont-have. O log diz &lt;code&gt;Rejecting auth&lt;/code&gt;. Auth inválido repetido também é recusado, com outra linha de log.&lt;/p&gt;

&lt;p&gt;A interface do authoriser é uma função:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;allowRead&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Cid&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Cid&lt;/span&gt; &lt;span class="n"&gt;sourceNodeId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;Want&lt;/code&gt; carrega o CID e um &lt;code&gt;authHex&lt;/code&gt; opcional. String vazia vira &lt;code&gt;Optional.empty()&lt;/code&gt;. Na API HTTP, o parâmetro &lt;code&gt;auth&lt;/code&gt; de &lt;code&gt;block/get&lt;/code&gt; preenche esse campo, e &lt;code&gt;peers&lt;/code&gt; limita a quem pedir. &lt;code&gt;persist&lt;/code&gt; falso pede o bloco sem gravá-lo no store local. O padrão da API é gravar.&lt;/p&gt;

&lt;p&gt;O BAT em si está em &lt;code&gt;src/main/java/org/peergos/blockstore/auth/Bat.java&lt;/code&gt;. São 32 bytes. Comprimento diferente lança &lt;code&gt;Invalid BAT length&lt;/code&gt;. O comentário da classe o descreve como a chave secreta de uma assinatura no estilo AWS S3 V4 (Simple Storage Service, assinatura versão 4), usada para autorizar a leitura do bloco. O processo &lt;code&gt;Nabu&lt;/code&gt; passa o authoriser &lt;code&gt;(c, p, a) -&amp;gt; CompletableFuture.completedFuture(true)&lt;/code&gt;. Quem embute o nó troca essa função pela política da aplicação.&lt;/p&gt;

&lt;p&gt;O que fica de fora: o Nabu implementa Bitswap 1.2.0 com o campo de auth que o Peergos usa. Não há um mercado de blocos nem pagamento dentro do protocolo. Um bloco maior que o teto da mensagem não cabe numa resposta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/entrar-na-rede-2955"&gt;Entrar na rede&lt;/a&gt;. O próximo é &lt;a href="https://dev.to/pedropietro/a-dht-kademlia-32ej"&gt;A DHT Kademlia&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Entrar na rede</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:02:57 +0000</pubDate>
      <link>https://dev.to/pedropietro/entrar-na-rede-2955</link>
      <guid>https://dev.to/pedropietro/entrar-na-rede-2955</guid>
      <description>&lt;p&gt;Na série IPFS com o Nabu, o host já sabe escutar. Este post é o que acontece quando o processo chama &lt;code&gt;start&lt;/code&gt;: achar outros pares e descobrir se o resto da rede consegue discar de volta.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;Um nó novo não conhece ninguém. O caderno de endereços começa vazio. Mesmo depois de conhecer alguém, o endereço em que ele escuta pode ser inútil para o mundo. &lt;code&gt;0.0.0.0&lt;/code&gt; é um bind local, não um destino. Um NAT (Network Address Translation) residencial reescreve o IP (Internet Protocol) e a porta de saída. Os pares de fora precisam de um endereço que de fato atenda. Enquanto isso não existe, o nó só fala com quem ele mesmo disca.&lt;/p&gt;

&lt;p&gt;A proposta é uma sequência. Primeiro, alguns pares já conhecidos enchem a tabela. Depois, o nó descobre o próprio endereço externo. Se continuar inalcançável, pede emprestado o caminho de um par que é público e tenta furar o NAT.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;A entrada tem quatro movimentos.&lt;/p&gt;

&lt;p&gt;O bootstrap é a lista de pares já conhecidos, em geral os nós públicos do libp2p. Um endereço &lt;code&gt;/dnsaddr/bootstrap.libp2p.io/...&lt;/code&gt; ainda não é discável. DNS (Domain Name System) é o sistema que traduz nomes em endereços. O componente &lt;code&gt;dnsaddr&lt;/code&gt; consulta o registro TXT (um texto publicado no DNS) de &lt;code&gt;_dnsaddr.&lt;/code&gt; mais o domínio e troca aquilo por multiaddrs concretos, com IP, transporte e PeerId. O nó disca esses pares e pede à Kademlia os vizinhos mais próximos. A tabela de roteamento deixa de estar vazia. DHT (Distributed Hash Table) é essa tabela, repartida entre os pares.&lt;/p&gt;

&lt;p&gt;O mDNS (multicast DNS) procura pares na rede local, sem passar pela internet. O anúncio usa o nome de serviço &lt;code&gt;_ipfs-discovery._udp.local.&lt;/code&gt;. Quem estiver na mesma rede e escutar esse nome responde com o PeerId e os multiaddrs.&lt;/p&gt;

&lt;p&gt;O alcance responde à pergunta "alguém de fora consegue me discar?". O identify já entrega o observed address, o endereço que o outro par viu. O AutoNAT pede a um par que tente discar de volta num candidato. Se a discagem volta, o endereço é público. UPnP (Universal Plug and Play) e NAT-PMP (NAT Port Mapping Protocol) pedem ao roteador de casa um mapeamento da porta de swarm para uma porta externa. Swarm é a lista de portas em que o nó aceita conexões. O endereço mapeado entra na lista de candidatos.&lt;/p&gt;

&lt;p&gt;Quando nada disso torna o nó discável, o circuit relay empresta o caminho de um par público. O multiaddr ganha o componente &lt;code&gt;/p2p-circuit&lt;/code&gt;: primeiro o endereço do relay, depois o PeerId de destino. O DCUtR (Direct Connection Upgrade through Relay) usa essa conexão emprestada para combinar um furo simultâneo. Os dois lados discam um ao outro ao mesmo tempo, e o NAT de cada um aceita o pacote porque acabou de ver saída para aquele destino. Se der certo, a conversa deixa o relay.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  boot[bootstrap] --&amp;gt; dht[routingTable]
  mdns[mdns] --&amp;gt; dht
  observe[identifyObservedAddr] --&amp;gt; nat[autonatDialback]
  upnp[upnpOrNatPmp] --&amp;gt; nat
  nat --&amp;gt; relay[circuitRelayIfPrivate]
  relay --&amp;gt; dcutr[directUpgrade]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;EmbeddedIpfs.start&lt;/code&gt; faz a sequência, em &lt;code&gt;src/main/java/org/peergos/EmbeddedIpfs.java&lt;/code&gt;. Registra um shutdown hook que chama &lt;code&gt;stop&lt;/code&gt;. Sobe o host com &lt;code&gt;node.start()&lt;/code&gt;. Registra o identify já com a lista &lt;code&gt;announce&lt;/code&gt; convertida em multiaddrs. Se &lt;code&gt;bootstrap&lt;/code&gt; está vazio, o log avisa que o nó não entra na DHT global. &lt;code&gt;dht.bootstrapRoutingTable&lt;/code&gt; recebe um filtro que descarta qualquer endereço cuja string contém &lt;code&gt;/wss/&lt;/code&gt; (WebSocket seguro, um transporte que este host não registra). Em seguida, se o chamador não pediu bootstrap assíncrono, &lt;code&gt;dht.bootstrap&lt;/code&gt; caminha a Kademlia. &lt;code&gt;startBootstrapThread&lt;/code&gt; repete o trabalho.&lt;/p&gt;

&lt;p&gt;O mDNS usa &lt;code&gt;MDnsDiscovery&lt;/code&gt; com o serviço &lt;code&gt;_ipfs-discovery._udp.local.&lt;/code&gt; e intervalo de 60 segundos. No handler, o PeerId do anúncio passa por um recorte que o código comenta como ajuste do valor recebido. Se não somos nós mesmos, o nó abre um stream Kademlia com &lt;code&gt;dial&lt;/code&gt;, pede &lt;code&gt;closerPeers&lt;/code&gt; a partir do nosso PeerId e grava os endereços do anúncio no caderno com &lt;code&gt;addAddrs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DnsAddr.resolve&lt;/code&gt;, em &lt;code&gt;src/main/java/org/peergos/protocol/dnsaddr/DnsAddr.java&lt;/code&gt;, só age quando a string começa com &lt;code&gt;/dnsaddr/&lt;/code&gt;. O restante da lista de bootstrap passa intacto. O domínio vai para uma consulta TXT em &lt;code&gt;_dnsaddr.&lt;/code&gt; mais o nome. O sufixo do multiaddr original é reaplicado, e &lt;code&gt;/ipfs/&lt;/code&gt; também é aceito como &lt;code&gt;/p2p/&lt;/code&gt;. &lt;code&gt;bootstrapRoutingTable&lt;/code&gt; faz essa resolução em paralelo, grava cada endereço resolvido no caderno e disca com timeout de 5 segundos por futuro. O retorno é a contagem de discagens que completaram.&lt;/p&gt;

&lt;p&gt;O redirecionamento de porta é opt-in. &lt;code&gt;enablePortForwarding&lt;/code&gt; precisa ser chamado antes de &lt;code&gt;start&lt;/code&gt;. O processo &lt;code&gt;Nabu&lt;/code&gt; faz isso quando o config traz &lt;code&gt;Addresses.UPnP&lt;/code&gt; verdadeiro. &lt;code&gt;startPortForwarders&lt;/code&gt; só corre se a flag está ligada e já existem &lt;code&gt;swarmAddresses&lt;/code&gt; e &lt;code&gt;ReachabilityManager&lt;/code&gt;. &lt;code&gt;PortForwarder.forSwarm&lt;/code&gt; abre um mapeador por porta distinta de TCP (Transmission Control Protocol) ou UDP (User Datagram Protocol), com vida de 3600 segundos e nova tentativa a cada 60 segundos se o roteador falhar. O multiaddr externo volta para &lt;code&gt;reachability.addLocalCandidate&lt;/code&gt; e também entra no caderno do próprio nó.&lt;/p&gt;

&lt;p&gt;O AutoNAT v2 sobe dentro de &lt;code&gt;HostBuilder.build&lt;/code&gt; quando o protocolo está na lista, e &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; inclui &lt;code&gt;new AutonatProtocol.Binding()&lt;/code&gt; e &lt;code&gt;AutonatV2.protocols()&lt;/code&gt;. O cliente monta candidatos em &lt;code&gt;externalAddressCandidates&lt;/code&gt;. Entram os endereços que pares já observaram, o IP observado casado com a porta TCP de escuta, os mapeamentos que o &lt;code&gt;ReachabilityManager&lt;/code&gt; guardou como candidatos locais, e os endereços de escuta que &lt;code&gt;PeerAddresses.isPublic&lt;/code&gt; aceita e que não contêm &lt;code&gt;p2p-circuit&lt;/code&gt;. O comentário no código registra uma assimetria. A discagem QUIC reaproveita a porta de escuta, então o observed address do QUIC já é o mapeamento real. A discagem TCP ainda sai de porta efêmera, e o candidato TCP é uma previsão: IP observado mais a porta em que o nó escuta.&lt;/p&gt;

&lt;p&gt;Relay e DCUtR também entram em &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt;, pelas chamadas &lt;code&gt;enableRelay(Relay.candidateRelaySource(dht))&lt;/code&gt; e &lt;code&gt;enableDcutr()&lt;/code&gt;. &lt;code&gt;enableRelay&lt;/code&gt; cria um &lt;code&gt;GatingRelayManager.reachabilityGated&lt;/code&gt; com limite de 5 reservas simultâneas. Esse manager só concede reserva enquanto o próprio nó está publicamente alcançável, para um nó atrás de NAT não se anunciar como relay. A reserva padrão cabe 1 MiB e dura 120 segundos. O comentário explica o tamanho: o padrão da biblioteca, 4096 bytes, mal completa multistream, Noise e yamux, e não sobra espaço para o DCUtR. O AutoRelay, ainda em &lt;code&gt;HostBuilder.build&lt;/code&gt;, escuta o &lt;code&gt;ReachabilityManager&lt;/code&gt;. No estado &lt;code&gt;PRIVATE&lt;/code&gt; pede duas reservas e chama &lt;code&gt;ensureEnoughCurrentRelays&lt;/code&gt;. No estado &lt;code&gt;PUBLIC&lt;/code&gt; zera o pedido.&lt;/p&gt;

&lt;p&gt;O DCUtR abre o stream &lt;code&gt;/libp2p/dcutr&lt;/code&gt; no lado que recebeu a conexão, e só se o endereço remoto contém &lt;code&gt;p2p-circuit&lt;/code&gt;. O lado de dentro da conexão relayed envia CONNECT com os endereços discáveis, mede a ida e volta até a resposta e, meio RTT (round-trip time, o tempo de ida e volta) depois, disca. O outro lado disca ao receber SYNC. O resultado do furo volta para &lt;code&gt;ReachabilityManager.recordHolePunchOutcome&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O que fica de fora: o README ainda lista circuit relay, DCUtR e AutoRelay como trabalho futuro. O &lt;code&gt;build&lt;/code&gt; do host já liga os três. Não há relay v1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/transportes-criptografia-e-multiplexacao-3a8i"&gt;Transportes, criptografia e multiplexação&lt;/a&gt;. O próximo é &lt;a href="https://dev.to/pedropietro/bitswap-o-pedido-de-um-bloco-14db"&gt;Bitswap, o pedido de um bloco&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Transportes, criptografia e multiplexação</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:02:22 +0000</pubDate>
      <link>https://dev.to/pedropietro/transportes-criptografia-e-multiplexacao-3a8i</link>
      <guid>https://dev.to/pedropietro/transportes-criptografia-e-multiplexacao-3a8i</guid>
      <description>&lt;p&gt;Na série IPFS com o Nabu, o par já tem PeerId (o identificador estável) e multiaddr (o endereço em camadas). Este post é a conexão em si: o que atravessa o fio antes de qualquer bloco.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;Bitswap, Kademlia, identify e HTTP (Hypertext Transfer Protocol) entre pares precisam falar ao mesmo tempo com o mesmo par. Abrir uma conexão TCP (Transmission Control Protocol) por protocolo repetiria o handshake e atravessaria o NAT (Network Address Translation, a tradução de portas do roteador) várias vezes. O fio também é público. Os bytes precisam de sigilo e de autenticação: no fim do handshake, cada lado sabe o PeerId do outro.&lt;/p&gt;

&lt;p&gt;A proposta da pilha libp2p é empilhar três trabalhos distintos. Um carrega bytes. Outro cifra e autentica. O terceiro reparte a conexão cifrada em conversas paralelas.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;O transporte carrega bytes. TCP é um fluxo confiável: o que se perde é retransmitido, e a ordem se preserva. UDP (User Datagram Protocol) entrega datagramas, sem essa garantia. QUIC, escrito &lt;code&gt;quic-v1&lt;/code&gt; no multiaddr, anda sobre UDP, traz criptografia própria e já multiplexa várias conversas no mesmo fluxo. O nome QUIC deixou de ser sigla. Nasceu como Quick UDP Internet Connections. O Nabu escuta TCP e QUIC quando o endereço de swarm pede os dois. Swarm, aqui, é a lista de endereços em que o nó aceita conexões de outros pares. Um endereço típico de TCP é &lt;code&gt;/ip4/0.0.0.0/tcp/4001&lt;/code&gt;. O de QUIC é &lt;code&gt;/ip4/0.0.0.0/udp/4001/quic-v1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O canal seguro autentica o PeerId. No TCP, isso é uma camada à parte, porque o TCP sozinho não cifra. O Noise XX faz o handshake, deriva chaves de sessão e amarra a chave pública do par. XX é o padrão do handshake: as duas identidades são transmitidas e cifradas no meio da troca. No QUIC a criptografia já vem no transporte. O host registra ECDSA (Elliptic Curve Digital Signature Algorithm) como o provedor que o QUIC do jvm-libp2p usa nessa pilha.&lt;/p&gt;

&lt;p&gt;O muxer reparte uma conexão TCP cifrada em vários streams. Cada protocolo abre o seu, como várias ligações dentro de um único cano. Yamux é o muxer principal. Mplex continua registrado para falar com pares que só oferecem esse. Um stream é barato em relação a uma conexão nova. É por isso que identify, Bitswap e Kademlia convivem depois de um único handshake. A abertura de cada stream negocia um identificador de protocolo, uma string como &lt;code&gt;/ipfs/bitswap/1.2.0&lt;/code&gt;. Os dois lados precisam anunciar a mesma string.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TB
  app[bitswapAndDhtAndHttp] --&amp;gt; mux[yamuxOrMplex]
  mux --&amp;gt; secure[noiseOnTcp]
  secure --&amp;gt; tcp[tcp]
  app --&amp;gt; quic[quicV1]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;HostBuilder.build&lt;/code&gt; monta a pilha em &lt;code&gt;src/main/java/org/peergos/HostBuilder.java&lt;/code&gt;. Se ninguém passou muxers, entram yamux e mplex, nessa ordem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;muxers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;muxers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StreamMuxerProtocol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getYamux&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MAX_BUFFERED_CONNECTION_WRITES&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;StreamMuxerProtocol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMplex&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;MAX_BUFFERED_CONNECTION_WRITES&lt;/code&gt; vale 128 MiB. O comentário no código explica o número. O yamux limita o buffer de escrita por conexão, não por stream, e um stream que estoura o buffer é resetado em vez de esperar. O padrão de 10 MiB cabia mal uma resposta HTTP de 4 MB. Três respostas grandes na mesma conexão derrubavam umas às outras: numa medida do próprio código, 1053 de 15740 respostas sobreviviam, contra 100% com duas. &lt;code&gt;Stream.writeAndFlush&lt;/code&gt; não devolve um future, então o estouro era invisível para quem chamava. 128 MiB é um teto, não uma alocação. A constante fica acima de &lt;code&gt;HttpProtocol.MAX_BODY_SIZE&lt;/code&gt; vezes o número de respostas grandes esperadas em voo para um único par.&lt;/p&gt;

&lt;p&gt;Dentro do builder do jvm-libp2p, o método olha a lista de multiaddrs já convertidos. QUIC só é registrado se algum endereço tem o componente &lt;code&gt;quic-v1&lt;/code&gt;. TCP entra sempre, com &lt;code&gt;TcpTransport::new&lt;/code&gt;. O canal seguro acrescentado é &lt;code&gt;NoiseXXSecureChannel&lt;/code&gt;. A linha do TLS (Transport Layer Security) está comentada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toListen&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;anyMatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Protocol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;QUICV1&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSecureTransports&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;QuicTransport:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="no"&gt;ECDSA&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTransports&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;TcpTransport:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSecureChannels&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;NoiseXXSecureChannel:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;//            b.getSecureChannels().add(TlsSecureChannel::new);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O README ainda cita TLS com negociação antecipada de muxer via ALPN (Application-Layer Protocol Negotiation, a escolha do próximo protocolo durante o handshake TLS). O host que &lt;code&gt;build&lt;/code&gt; devolve hoje cifra TCP com Noise. QUIC cifra por conta própria, pelo transporte registrado acima.&lt;/p&gt;

&lt;p&gt;Depois da cifra, o método percorre os &lt;code&gt;ProtocolBinding&lt;/code&gt; que &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; juntou: ping, AutoNAT, Bitswap, Kademlia, e o HTTP se houver handler. Cada binding entra em &lt;code&gt;b.getProtocols()&lt;/code&gt;. Se o protocolo implementa &lt;code&gt;AddressBookConsumer&lt;/code&gt;, recebe o caderno. Se implementa &lt;code&gt;ConnectionHandler&lt;/code&gt;, entra na lista de handlers da conexão. Em seguida o host escuta cada string de &lt;code&gt;listenAddrs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; ainda calcula duas flags antes de criar a Kademlia: &lt;code&gt;quicEnabled&lt;/code&gt; se algum swarm addr tem &lt;code&gt;QUICV1&lt;/code&gt;, &lt;code&gt;tcpEnabled&lt;/code&gt; se algum tem &lt;code&gt;TCP&lt;/code&gt;. A Kademlia guarda as flags. &lt;code&gt;hasTransportOverlap&lt;/code&gt; aceita um endereço se ele tem TCP e este nó fala TCP, ou se tem QUIC sem o componente WebTransport e este nó fala QUIC. O conjunto ainda recusa endereço que contém &lt;code&gt;p2p-circuit&lt;/code&gt;. O circuit relay entra por outro caminho, no post seguinte.&lt;/p&gt;

&lt;p&gt;O que fica de fora: WebTransport e WebSockets não são registrados. O bootstrap chega a descartar endereços que contêm &lt;code&gt;/wss/&lt;/code&gt;. Não há configuração para desligar o Noise e ligar o TLS. Quem quiser só TCP, ou só QUIC, escolhe isso na lista de multiaddrs de escuta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/identidade-e-enderecos-3c71"&gt;Identidade e endereços&lt;/a&gt;. O próximo é &lt;a href="https://dev.to/pedropietro/entrar-na-rede-2955"&gt;Entrar na rede&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Identidade e endereços</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:02:21 +0000</pubDate>
      <link>https://dev.to/pedropietro/identidade-e-enderecos-3c71</link>
      <guid>https://dev.to/pedropietro/identidade-e-enderecos-3c71</guid>
      <description>&lt;p&gt;Na série IPFS com o Nabu, o bloco já tem um CID (Content Identifier, o identificador do conteúdo). Falta nomear quem o guarda e onde esse par pode ser encontrado agora.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;Um CID diz o que o bloco é. Não diz quem o tem. A rede precisa de outra identidade: a do processo que fala libp2p, a pilha de rede par a par. Esse processo muda de IP (Internet Protocol, o endereço da máquina), abre outra porta e atravessa um NAT (Network Address Translation, a reescrita de endereços que o roteador faz entre a rede de casa e a internet).&lt;/p&gt;

&lt;p&gt;A proposta é separar as duas coisas. O nome do par sobrevive à mudança de rede. O endereço de rede é uma lista que se atualiza.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;A identidade é um par de chaves. A chave pública vira o PeerId. A chave privada assina o que só aquele par pode afirmar, entre outras coisas o registro IPNS (InterPlanetary Name System) de um post seguinte. O Nabu gera Ed25519 por padrão, um esquema de assinatura de chave pequena e rápida. Um PeerId também pode nascer de RSA (Rivest–Shamir–Adleman, o esquema clássico de chave pública). Os dois cabem no mesmo formato porque o PeerId é um multihash da chave pública: o tipo da chave e o digest viajam juntos. Quem vê o PeerId sabe qual algoritmo esperar na hora de verificar uma assinatura.&lt;/p&gt;

&lt;p&gt;O lugar onde o par escuta é um multiaddr, uma sequência de protocolos escritos num só texto. &lt;code&gt;/ip4/203.0.113.4/tcp/4001&lt;/code&gt; diz IPv4, TCP (Transmission Control Protocol, o fluxo confiável) e a porta 4001. &lt;code&gt;/ip4/0.0.0.0/udp/4001/quic-v1&lt;/code&gt; diz "escute em todas as interfaces", UDP (User Datagram Protocol) e QUIC versão 1. O &lt;code&gt;0.0.0.0&lt;/code&gt; é um bind local: serve para abrir o socket, e não é um destino que o resto da internet disca. O mesmo PeerId anuncia vários multiaddrs ao mesmo tempo. Trocar de rede troca a lista. O PeerId fica.&lt;/p&gt;

&lt;p&gt;Como a lista muda, cada nó guarda um caderno de endereços: PeerId para os multiaddrs vistos por último. A lista chega de três jeitos. O outro par diz, no protocolo identify, em quais endereços escuta. Uma conexão que nós mesmos discamos traz o endereço que acabou de funcionar. A DHT (Distributed Hash Table), mais adiante, devolve endereços quando perguntamos por um par.&lt;/p&gt;

&lt;p&gt;O identify também devolve o observed address, o endereço que o outro par observou em nós. Um nó atrás de NAT usa isso para descobrir o IP externo. O post sobre entrar na rede continua a partir daí.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;HostBuilder.generateIdentity&lt;/code&gt;, em &lt;code&gt;src/main/java/org/peergos/HostBuilder.java&lt;/code&gt;, cria a chave e deriva o PeerId.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;HostBuilder&lt;/span&gt; &lt;span class="nf"&gt;generateIdentity&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;setPrivKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Ed25519Kt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generateEd25519KeyPair&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getFirst&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;HostBuilder&lt;/span&gt; &lt;span class="nf"&gt;setPrivKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PrivKey&lt;/span&gt; &lt;span class="n"&gt;privKey&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;privKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;privKey&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;peerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PeerId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromPubKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;privKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;publicKey&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;generateIdentity&lt;/code&gt; pede um par Ed25519 à biblioteca e guarda só a metade privada. &lt;code&gt;setPrivKey&lt;/code&gt; deriva a pública e chama &lt;code&gt;PeerId.fromPubKey&lt;/code&gt;. A partir daí &lt;code&gt;getPeerId()&lt;/code&gt; e &lt;code&gt;getPrivateKey()&lt;/code&gt; devolvem os dois lados. &lt;code&gt;setIdentity(byte[])&lt;/code&gt; faz o caminho inverso: &lt;code&gt;KeyKt.unmarshalPrivateKey&lt;/code&gt; lê os bytes já gravados e passa por &lt;code&gt;setPrivKey&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O processo standalone persiste isso em &lt;code&gt;IdentitySection&lt;/code&gt;, no arquivo de config. O JSON (JavaScript Object Notation) tem &lt;code&gt;PeerID&lt;/code&gt; em base58, o alfabeto que o IPFS usa para PeerIds, e &lt;code&gt;PrivKey&lt;/code&gt; em base64. &lt;code&gt;fromJson&lt;/code&gt; decodifica os dois. Na subida, &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; chama &lt;code&gt;setIdentity(identity.privKeyProtobuf)&lt;/code&gt; e compara &lt;code&gt;builder.getPeerId()&lt;/code&gt; com &lt;code&gt;identity.peerId&lt;/code&gt;. Se divergirem, lança &lt;code&gt;PeerId invalid&lt;/code&gt; e o nó não sobe. A chave e o identificador anunciado têm de ser o mesmo par.&lt;/p&gt;

&lt;p&gt;O caderno padrão é &lt;code&gt;RamAddressBook&lt;/code&gt;, passado no construtor do &lt;code&gt;HostBuilder&lt;/code&gt;. Ele vive na memória do processo. Protocolos que precisam dele implementam &lt;code&gt;AddressBookConsumer&lt;/code&gt;. No fim de &lt;code&gt;build&lt;/code&gt;, cada protocolo da lista recebe &lt;code&gt;setAddressBook&lt;/code&gt;. Bitswap e Kademlia fazem isso, e repassam o caderno aos motores internos.&lt;/p&gt;

&lt;p&gt;O identify está no handler de conexão de &lt;code&gt;HostBuilder.build&lt;/code&gt;, registrado para toda conexão nova. O fluxo, na ordem do código:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lê o PeerId remoto da sessão já cifrada, &lt;code&gt;connection.secureSession().getRemoteId()&lt;/code&gt;. Nesse ponto o handshake já amarrou a chave.&lt;/li&gt;
&lt;li&gt;Consulta o caderno. Se a lista daquele par está vazia, ele é novo.&lt;/li&gt;
&lt;li&gt;Se fomos nós que discamos (&lt;code&gt;connection.isInitiator()&lt;/code&gt;), grava o endereço remoto dessa conexão com &lt;code&gt;addAddrs&lt;/code&gt;. Esse endereço acabou de ser discável.&lt;/li&gt;
&lt;li&gt;Abre um stream do protocolo identify e chama &lt;code&gt;id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Dos &lt;code&gt;listenAddrs&lt;/code&gt; que o remoto devolve, grava a lista no caderno. Esses são os endereços em que ele escuta, não a porta efêmera de uma conexão que chegou de fora. Guardar a porta de origem deixaria no caderno um destino que ninguém escuta.&lt;/li&gt;
&lt;li&gt;Se a resposta traz observed address, entrega a &lt;code&gt;ReachabilityManager.observeAddress&lt;/code&gt;, junto com o hash do par que observou.&lt;/li&gt;
&lt;li&gt;Se a lista de protocolos do remoto contém &lt;code&gt;/ipfs/kad/1.0.0&lt;/code&gt; e este nó ainda não o tinha discado, abre um stream Kademlia. A tabela de roteamento cresce com quem chega.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O que fica de fora: o caderno não é um banco. Reiniciar o processo esquece os endereços aprendidos. A identidade, no processo &lt;code&gt;Nabu&lt;/code&gt;, sobrevive porque está no config. Um &lt;code&gt;EmbeddedIpfs&lt;/code&gt; montado pela aplicação guarda a chave só se a aplicação a passar de novo em &lt;code&gt;IdentitySection&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/o-que-e-ipfs-5boi"&gt;O que é IPFS&lt;/a&gt;. O próximo é &lt;a href="https://dev.to/pedropietro/transportes-criptografia-e-multiplexacao-3a8i"&gt;Transportes, criptografia e multiplexação&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>O que é IPFS</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:01:42 +0000</pubDate>
      <link>https://dev.to/pedropietro/o-que-e-ipfs-5boi</link>
      <guid>https://dev.to/pedropietro/o-que-e-ipfs-5boi</guid>
      <description>&lt;p&gt;Este post abre a série IPFS com o Nabu. IPFS significa InterPlanetary File System. O assunto é o endereço do conteúdo: um bloco se chama pelo hash dos próprios bytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;A proposta do IPFS é trocar o "onde" pelo "o quê". Um URL (Uniform Resource Locator) da web diz em que servidor e em que caminho buscar um arquivo. Se o servidor sai do ar, ou se alguém troca o arquivo e mantém o caminho, o URL continua igual e o conteúdo muda. Quem recebe os bytes precisa confiar em quem os enviou.&lt;/p&gt;

&lt;p&gt;O IPFS propõe o contrário. O nome do dado é uma função dos bytes. Quem recebe calcula a função de novo. Se o resultado bate com o nome pedido, os bytes são aqueles, qualquer que seja o par que os entregou.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;Pegue a frase &lt;code&gt;olá&lt;/code&gt;. Os bytes UTF-8 passam por SHA-256 (Secure Hash Algorithm, 256 bits), uma função que espalha qualquer entrada num número de tamanho fixo. Um byte diferente muda o número. O mesmo texto produz sempre o mesmo número. Esse número, mais um rótulo de como ler os bytes, vira o CID (Content Identifier, identificador de conteúdo).&lt;/p&gt;

&lt;p&gt;Na prática os dados são partidos em blocos. Cada bloco ganha um CID. Um bloco pode conter links para outros blocos, também na forma de CIDs. O conjunto forma um DAG (Directed Acyclic Graph, grafo dirigido sem ciclos): as setas saem de um bloco para os filhos, e não há caminho que volte ao início. A raiz tem o seu próprio CID. Pedir a raiz e seguir os links reconstrói o conjunto.&lt;/p&gt;

&lt;p&gt;Um CID versão 1 junta três escolhas. A versão diz qual é o formato do identificador. O codec diz como os bytes estão organizados: &lt;code&gt;raw&lt;/code&gt; são bytes opacos, &lt;code&gt;dag-cbor&lt;/code&gt; é um mapa CBOR (Concise Binary Object Representation, um formato binário compacto) com links, &lt;code&gt;dag-pb&lt;/code&gt; é o protobuf usado pelos arquivos clássicos do IPFS. Essas formas de organizar bytes com links fazem parte do IPLD (InterPlanetary Linked Data). O multihash diz qual função de hash foi usada e traz o digest. Por isso os mesmos bytes com codecs diferentes têm CIDs diferentes. O hash sozinho identifica os bytes. O CID identifica os bytes mais a forma de lê-los.&lt;/p&gt;

&lt;p&gt;O Nabu guarda e troca esses blocos. Um caminho de arquivo Unix fica para a aplicação que monta o DAG por cima.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  bytes[bytes] --&amp;gt; hash[sha256]
  hash --&amp;gt; cid[cidV1]
  cid --&amp;gt; link[linksInsideOtherBlocks]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;HashedBlock&lt;/code&gt;, em &lt;code&gt;src/main/java/org/peergos/HashedBlock.java&lt;/code&gt;, é o par que a rede devolve: o CID e os bytes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HashedBlock&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Cid&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quem grava um bloco calcula o CID. Não há uma tabela à parte de "nome para bytes". &lt;code&gt;FileBlockstore.put&lt;/code&gt; faz a conta com CID versão 1 e SHA-256:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Cid&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Cid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Codec&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Cid&lt;/span&gt; &lt;span class="n"&gt;cid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Cid&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Multihash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sha2_256&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O primeiro argumento, &lt;code&gt;1&lt;/code&gt;, é a versão do CID. &lt;code&gt;codec&lt;/code&gt; vem de quem chama: a API HTTP pede o nome IPLD em &lt;code&gt;format&lt;/code&gt; e traduz com &lt;code&gt;Cid.Codec.lookupIPLDName&lt;/code&gt;. &lt;code&gt;Multihash.Type.sha2_256&lt;/code&gt; escolhe a função. &lt;code&gt;Hash.sha256(block)&lt;/code&gt; é o digest. Os bytes iguais com &lt;code&gt;Raw&lt;/code&gt; e com &lt;code&gt;DagCbor&lt;/code&gt; produzem CIDs diferentes, e os dois podem coexistir no mesmo store.&lt;/p&gt;

&lt;p&gt;A interface &lt;code&gt;Blockstore&lt;/code&gt; oferece &lt;code&gt;has(Cid)&lt;/code&gt;, &lt;code&gt;get(Cid)&lt;/code&gt;, &lt;code&gt;put(byte[], Codec)&lt;/code&gt; e &lt;code&gt;rm(Cid)&lt;/code&gt;. &lt;code&gt;has&lt;/code&gt; responde se a cópia local existe. &lt;code&gt;get&lt;/code&gt; devolve &lt;code&gt;Optional&amp;lt;byte[]&amp;gt;&lt;/code&gt;, vazio quando o CID não está aqui. &lt;code&gt;put&lt;/code&gt; devolve o CID calculado, não um caminho. &lt;code&gt;rm&lt;/code&gt; apaga essa cópia local. O conteúdo com o mesmo CID continua válido em qualquer outro par que ainda o tenha.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EmbeddedIpfs.getBlocks&lt;/code&gt; aplica a regra na hora de buscar. Para cada &lt;code&gt;Want&lt;/code&gt; (o pedido: um CID e, se houver, um segredo de autorização), o método pergunta &lt;code&gt;blockstore.has&lt;/code&gt;. O que já está local entra numa lista e é lido com &lt;code&gt;blockstore.get&lt;/code&gt;. Se o &lt;code&gt;has&lt;/code&gt; disse sim e o &lt;code&gt;get&lt;/code&gt; veio vazio, ou se o &lt;code&gt;get&lt;/code&gt; lançou, o pedido volta para a lista remota. Se essa lista termina vazia, o método devolve só o que achou no disco. Se sobra pedido e não há &lt;code&gt;BlockService&lt;/code&gt;, lança &lt;code&gt;Blocks not found locally&lt;/code&gt;. Com o serviço presente, que no nó completo é o Bitswap, concatena os blocos locais com os que a rede devolver.&lt;/p&gt;

&lt;p&gt;O que fica de fora aqui: o Nabu não implementa o sistema de arquivos FUSE (Filesystem in Userspace) do Kubo, nem &lt;code&gt;ipfs add&lt;/code&gt; de um diretório. A API HTTP cobre operações de bloco, refs locais, um pedaço da DHT (Distributed Hash Table, a tabela que aponta quem tem o CID) e a leitura IPNS (InterPlanetary Name System, o nome mutável). Quem quiser um diretório endereçado por conteúdo monta os blocos e os links na aplicação.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;O anterior é &lt;a href="https://dev.to/pedropietro/a-serie-ipfs-com-o-nabu-41km"&gt;A série IPFS com o Nabu&lt;/a&gt;. O próximo é &lt;a href="https://dev.to/pedropietro/identidade-e-enderecos-3c71"&gt;Identidade e endereços&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>A série IPFS com o Nabu</title>
      <dc:creator>Pedro Pietroluongo</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:01:40 +0000</pubDate>
      <link>https://dev.to/pedropietro/a-serie-ipfs-com-o-nabu-41km</link>
      <guid>https://dev.to/pedropietro/a-serie-ipfs-com-o-nabu-41km</guid>
      <description>&lt;p&gt;Esta série apresenta o IPFS (InterPlanetary File System, o sistema de arquivos interplanetário) para quem está chegando agora, usando o &lt;a href="https://github.com/Peergos/nabu" rel="noopener noreferrer"&gt;Nabu&lt;/a&gt; como implementação concreta e minha versão &lt;a href="https://github.com/pedropietro/nabu" rel="noopener noreferrer"&gt;https://github.com/pedropietro/nabu&lt;/a&gt;. O Nabu é um nó IPFS em Java, usado em produção pelo &lt;a href="https://peergos.org" rel="noopener noreferrer"&gt;Peergos&lt;/a&gt;. Cada post explica o conceito, soletra as siglas e, no fim, percorre a classe que o realiza.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposta
&lt;/h2&gt;

&lt;p&gt;A proposta da série é separar um nó em peças que dá para ler uma de cada vez. Na documentação, IPFS, libp2p, CID, DHT e Bitswap aparecem juntos. Libp2p é a biblioteca de rede par a par em que o IPFS se apoia: conexão, identidade e protocolos. CID (Content Identifier) é o identificador do conteúdo, o nome de um bloco. DHT (Distributed Hash Table) é a tabela de hash distribuída que responde "quem tem isto?". Bitswap é o protocolo que pede e entrega os bytes desse bloco.&lt;/p&gt;

&lt;p&gt;Um processo real faz tudo isso ao mesmo tempo: escuta endereços, troca blocos, anuncia conteúdo e guarda registros de nomes. A série propõe uma ordem. Cada post entrega uma sigla com o nome por extenso e o trecho de Java que a implementa.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Demonstração
&lt;/h2&gt;

&lt;p&gt;O caminho de um bloco, do nome até a chamada HTTP (Hypertext Transfer Protocol, o protocolo da web):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O endereço do conteúdo. O hash dos bytes vira um CID.&lt;/li&gt;
&lt;li&gt;A identidade de quem guarda o bloco: uma chave, um PeerId (identificador do par) e um multiaddr (o endereço de rede em camadas).&lt;/li&gt;
&lt;li&gt;A conexão: TCP (Transmission Control Protocol), QUIC (transporte sobre UDP, User Datagram Protocol), cifra e vários protocolos no mesmo fio.&lt;/li&gt;
&lt;li&gt;A entrada na rede: achar outros pares e descobrir se eles conseguem discar de volta, atravessando NAT (Network Address Translation, a tradução de endereços que o roteador de casa faz).&lt;/li&gt;
&lt;li&gt;O pedido do bloco, via Bitswap.&lt;/li&gt;
&lt;li&gt;A descoberta de quem tem o bloco, via DHT Kademlia.&lt;/li&gt;
&lt;li&gt;Um nome que muda, o IPNS (InterPlanetary Name System), apontando para o CID atual.&lt;/li&gt;
&lt;li&gt;O disco, ou o S3 (Simple Storage Service), onde a cópia fica.&lt;/li&gt;
&lt;li&gt;A API (Application Programming Interface) HTTP local, no estilo do Kubo, a implementação de referência do IPFS em Go.&lt;/li&gt;
&lt;li&gt;HTTP carregado dentro do libp2p, de um par para outro.&lt;/li&gt;
&lt;li&gt;Os programas de exemplo do repositório, que exercitam os passos anteriores.
&lt;/li&gt;
&lt;/ol&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  intro[whatIsIpfs] --&amp;gt; id[identity]
  id --&amp;gt; transports[transports]
  transports --&amp;gt; reachability[reachability]
  reachability --&amp;gt; bitswap[bitswap]
  bitswap --&amp;gt; dht[dht]
  dht --&amp;gt; ipns[ipns]
  ipns --&amp;gt; store[blockstore]
  store --&amp;gt; api[api]
  api --&amp;gt; http[p2pHttp]
  http --&amp;gt; examples[examples]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Os arquivos têm nome em inglês de propósito. Uma tradução futura copia &lt;code&gt;docs/artigos/pt/&lt;/code&gt; para &lt;code&gt;docs/artigos/en/&lt;/code&gt; e reescreve a prosa. Os links relativos, os blocos de código e os identificadores de protocolo permanecem. O vocabulário travado está em &lt;code&gt;docs/artigos/glossary.yml&lt;/code&gt;. Títulos e descrições nos dois idiomas estão em &lt;code&gt;docs/artigos/series.yml&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Nabu
&lt;/h2&gt;

&lt;p&gt;O nó inteiro cabe em &lt;code&gt;EmbeddedIpfs&lt;/code&gt;, em &lt;code&gt;src/main/java/org/peergos/EmbeddedIpfs.java&lt;/code&gt;. Os campos públicos são o mapa da série:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Host&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Blockstore&lt;/span&gt; &lt;span class="n"&gt;blockstore&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BlockService&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;blockRetriever&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;RecordStore&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Kademlia&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HttpProtocol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Binding&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p2pHttp&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;node&lt;/code&gt; é o host libp2p: identidade, transportes e a lista de protocolos. &lt;code&gt;blockstore&lt;/code&gt; guarda os blocos e responde &lt;code&gt;has&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt; e &lt;code&gt;put&lt;/code&gt; por CID. &lt;code&gt;blockRetriever&lt;/code&gt; pede à rede o que o store local não tem. No nó completo ele é um &lt;code&gt;BitswapBlockService&lt;/code&gt;. &lt;code&gt;records&lt;/code&gt; guarda registros IPNS. &lt;code&gt;dht&lt;/code&gt; é a Kademlia, protocolo &lt;code&gt;/ipfs/kad/1.0.0&lt;/code&gt;. &lt;code&gt;p2pHttp&lt;/code&gt; é o HTTP entre pares, protocolo &lt;code&gt;/http&lt;/code&gt;, e só existe quando a aplicação registra um handler.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; monta esse conjunto numa ordem fixa. Recebe o store de registros, o blockstore, a flag &lt;code&gt;provideBlocks&lt;/code&gt; (se este nó anuncia os CIDs que grava), os multiaddrs de escuta, o bootstrap, a &lt;code&gt;IdentitySection&lt;/code&gt; e o &lt;code&gt;BlockRequestAuthoriser&lt;/code&gt;. Por dentro cria um &lt;code&gt;RamAddressBook&lt;/code&gt;, restaura a chave, instancia &lt;code&gt;KademliaEngine&lt;/code&gt; com um &lt;code&gt;RamProviderStore&lt;/code&gt; de 10.000 entradas, e registra ping, AutoNAT (o protocolo que testa se o nó é discável de fora), Bitswap, a DHT e, se houver handler, o HTTP. Em seguida chama &lt;code&gt;enableRelay&lt;/code&gt; e &lt;code&gt;enableDcutr&lt;/code&gt;. &lt;code&gt;start&lt;/code&gt; sobe o host, faz o bootstrap e liga o mDNS (multicast DNS, a descoberta na rede local).&lt;/p&gt;

&lt;p&gt;Dois pontos em que o README e o código divergem, e a série segue o código. &lt;code&gt;HostBuilder.create&lt;/code&gt; e &lt;code&gt;EmbeddedIpfs.build&lt;/code&gt; chamam &lt;code&gt;enableRelay&lt;/code&gt; e &lt;code&gt;enableDcutr&lt;/code&gt;. DCUtR significa Direct Connection Upgrade through Relay: um furo de NAT combinado por uma conexão emprestada. O canal seguro registrado em &lt;code&gt;HostBuilder.build&lt;/code&gt; é Noise. A linha que registraria TLS (Transport Layer Security) está comentada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nesta série
&lt;/h2&gt;

&lt;p&gt;Não há post anterior. O próximo é &lt;a href="https://dev.to/pedropietro/o-que-e-ipfs-5boi"&gt;O que é IPFS&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/o-que-e-ipfs-5boi"&gt;O que é IPFS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/identidade-e-enderecos-3c71"&gt;Identidade e endereços&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/transportes-criptografia-e-multiplexacao-3a8i"&gt;Transportes, criptografia e multiplexação&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/entrar-na-rede-2955"&gt;Entrar na rede&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/bitswap-o-pedido-de-um-bloco-14db"&gt;Bitswap, o pedido de um bloco&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/a-dht-kademlia-32ej"&gt;A DHT Kademlia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/ipns-um-nome-que-muda-2k0n"&gt;IPNS, um nome que muda&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/onde-o-bloco-mora-3e47"&gt;Onde o bloco mora&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/a-api-http-e-como-rodar-o-nabu-3lbm"&gt;A API HTTP e como rodar o Nabu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/http-entre-pares-5g2f"&gt;HTTP entre pares&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pedropietro/os-exemplos-do-repositorio-170o"&gt;Os exemplos do repositório&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ipfs</category>
      <category>libp2p</category>
      <category>java</category>
      <category>braziliandevs</category>
    </item>
  </channel>
</rss>
