blockchain Ethereum locale con Docker

blockchain Ethereum locale con Docker

In questo articolo costruiamo una piccola blockchain Ethereum locale, senza spendere gas reale, senza toccare la mainnet e senza affidarci a servizi cloud.

L’idea e’ semplice: invece di parlare della blockchain come se fosse una cosa lontana, la portiamo dentro il nostro computer. Creiamo una rete Ethereum privata con 5 nodi, facciamo girare transazioni tra account diversi e poi usiamo una LLM locale, tramite Ollama, per leggere il traffico e riassumere cosa sta succedendo.

Quindi non sara’ solo teoria. Alla fine avremo:

  • una rete Ethereum locale avviata con Docker;
  • almeno 5 nodi Ethereum che partecipano alla stessa rete;
  • account con ETH finti gia’ caricati nel genesis;
  • transazioni tra chiavi pubbliche diverse;
  • messaggi inseriti dentro le transazioni;
  • un file di log con hash, mittente, destinatario, timestamp e blocco;
  • Ollama che analizza il traffico e produce un report leggibile.

Piccola nota prima di partire: quando diciamo “fork locale” in questo contesto, lo usiamo nel senso pratico da laboratorio. Non stiamo clonando la mainnet con tutto il suo stato, cosa che richiederebbe un endpoint RPC esterno e molta piu’ infrastruttura. Stiamo creando una rete Ethereum-compatible locale, cioe’ una rete che ragiona come Ethereum: account, blocchi, gas, transazioni, JSON-RPC, chiavi pubbliche e private.

E per imparare come funziona davvero, questa e’ spesso la strada migliore.

Cos’e’ una blockchain, spiegata semplice

Una blockchain e’ un registro condiviso.

Immaginiamo un quaderno. Ogni pagina del quaderno contiene una lista di operazioni: Andrea manda 1 token a Marco, Marco manda 0.5 token a Sara, Sara scrive un messaggio dentro una transazione, e cosi’ via.

La differenza e’ che questo quaderno non vive su un solo computer. Vive su tanti nodi.

Ogni nodo conserva una copia della catena e controlla che le nuove pagine, cioe’ i nuovi blocchi, rispettino le regole della rete.

Un blocco, semplificando, contiene:

  • un numero progressivo;
  • una lista di transazioni;
  • un timestamp;
  • l’hash del blocco precedente;
  • altri dati tecnici usati dal protocollo.

L’hash del blocco precedente e’ importante perche’ crea la catena. Se qualcuno prova a modificare un blocco vecchio, cambia il suo hash. E se cambia quell’hash, non tornano piu’ tutti i blocchi successivi.

Ecco perche’ la blockchain non e’ magica. E’ una combinazione molto intelligente di crittografia, rete distribuita e regole condivise.

Come funziona Ethereum dentro questo ragionamento

Ethereum aggiunge una cosa fondamentale: non gestisce solo trasferimenti di valore, ma anche codice.

Questo codice si chiama smart contract.

In questo tutorial non partiamo dagli smart contract, perche’ voglio prima far vedere la parte piu’ essenziale: account, nodi, transazioni e messaggi.

Ogni account Ethereum ha:

  • una chiave privata;
  • una chiave pubblica;
  • un indirizzo pubblico, quello che di solito inizia con 0x;
  • un saldo;
  • un nonce, cioe’ un contatore delle transazioni inviate.

La chiave privata firma le transazioni. L’indirizzo pubblico riceve fondi o messaggi. La rete verifica che la firma sia valida.

Il punto importante e’: se io mando una transazione da un account a un altro, i nodi della rete ricevono l’informazione, la propagano, la includono in un blocco e poi aggiornano lo stato.

In pratica, tutti aggiornano lo stesso quaderno.

Perche’ usare Docker per una blockchain locale

Docker e’ perfetto per questo tipo di esperimento perche’ ci evita di installare manualmente client Ethereum, dipendenze, configurazioni di rete, chiavi dei validatori e strumenti di monitoraggio.

Nel nostro caso useremo Docker tramite Kurtosis, uno strumento pensato proprio per creare ambienti complessi e ripetibili. Kurtosis avvia container Docker isolati, li collega tra loro e ci permette di tirare su una rete Ethereum privata con un solo comando.

La parte interessante e’ questa: non stiamo lanciando un singolo nodo finto. Stiamo creando piu’ nodi che devono stare d’accordo sulla stessa catena.

Architettura del laboratorio

Il laboratorio sara’ composto da tre blocchi:

  1. Rete Ethereum locale
    Avviata con Docker/Kurtosis, con 5 nodi.
  2. Script Node.js
    Invia transazioni tra account diversi e salva il traffico in un file JSON.
  3. Ollama
    Esegue una LLM locale che legge il traffico e produce un’analisi semplice: numero di transazioni, coppie mittente/destinatario, timestamp, messaggi e pattern.

Il flusso sara’ questo:

Account A -> Nodo 1 -> rete Ethereum locale -> Nodo 2/3/4/5
Account B -> Nodo 2 -> rete Ethereum locale -> blocchi condivisi
Account C -> Nodo 3 -> rete Ethereum locale -> log transazioni
Log JSON  -> Ollama -> analisi leggibile

Prerequisiti

Servono pochi strumenti:

  • Docker Desktop installato e avviato;
  • Node.js 20 o superiore;
  • Kurtosis CLI;
  • almeno 8 GB di RAM liberi, meglio 16 GB;
  • un terminale.

Per installare Kurtosis, usa la guida ufficiale in base al tuo sistema operativo. Su Windows conviene lavorare con PowerShell o WSL2.

Verifica che Docker funzioni:

docker version

Verifica Node.js:

node -v
npm -v

Verifica Kurtosis:

kurtosis version

Creiamo il progetto

Creiamo una cartella dedicata:

mkdir nodeai-ethereum-lab
cd nodeai-ethereum-lab

Dentro questa cartella metteremo:

nodeai-ethereum-lab/
  network_params.yaml
  package.json
  send-messages.mjs
  analyze-traffic.mjs
  tx-log.json

Configuriamo la rete Ethereum locale con 5 nodi

Creiamo il file network_params.yaml.

Questo file dice a Kurtosis che vogliamo 5 partecipanti Ethereum, tutti con execution client Geth e consensus client Lighthouse.

In piu’ creiamo 5 account gia’ caricati con ETH finti nel genesis. Useremo chiavi note da ambiente di sviluppo. Sono chiavi pubbliche, da laboratorio, non da usare mai su mainnet.

participants:
  - el_type: geth
    cl_type: lighthouse
    count: 5

network_params:
  network: kurtosis
  genesis_delay: 20
  seconds_per_slot: 12
  num_validator_keys_per_node: 32
  prefunded_accounts:
    "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266":
      balance: "100ETH"
    "0x70997970C51812dc3A010C7d01b50e0d17dc79C8":
      balance: "100ETH"
    "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC":
      balance: "100ETH"
    "0x90F79bf6EB2c4f870365E785982E1f101E93b906":
      balance: "100ETH"
    "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65":
      balance: "100ETH"

additional_services:
  - dora
  - spamoor

snooper_params:
  enabled: true

spamoor_params:
  spammers:
    - scenario: eoatx
      name: nodeai-background-transactions
      config:
        throughput: 2

Cosa abbiamo scritto?

  • participants crea i nodi della rete.
  • count: 5 significa che vogliamo cinque nodi con la stessa combinazione client.
  • prefunded_accounts carica saldo finto dentro alcuni account.
  • dora aggiunge un explorer locale.
  • spamoor genera traffico di background.
  • snooper_params.enabled abilita il tracciamento delle chiamate JSON-RPC.

Questa e’ gia’ una piccola rete Ethereum privata.

Avviamo la rete

Lanciamo:

kurtosis run --enclave nodeai-chain github.com/ethpandaops/ethereum-package --args-file ./network_params.yaml

La prima volta Docker scarichera’ diverse immagini. Qui serve un po’ di pazienza.

Quando il comando finisce, controlliamo i servizi:

kurtosis enclave inspect nodeai-chain

Vedrai una lista di container: execution client, consensus client, validator client e servizi aggiuntivi.

Quello che ci serve e’ l’endpoint RPC di almeno un execution client. Kurtosis di solito pubblica porte locali tipo:

http://127.0.0.1:xxxxx

Annota 5 endpoint RPC, uno per ogni nodo execution. Se vuoi partire in modo semplice, puoi usare anche un solo endpoint RPC per inviare tutte le transazioni. Ma per il nostro laboratorio useremo piu’ endpoint, cosi’ vediamo i nodi come punti di ingresso diversi nella stessa rete.

Impostiamo una variabile ambiente con gli RPC separati da virgola:

export RPC_URLS="http://127.0.0.1:PORTA_NODO_1,http://127.0.0.1:PORTA_NODO_2,http://127.0.0.1:PORTA_NODO_3,http://127.0.0.1:PORTA_NODO_4,http://127.0.0.1:PORTA_NODO_5"

Su PowerShell:

$env:RPC_URLS="http://127.0.0.1:PORTA_NODO_1,http://127.0.0.1:PORTA_NODO_2,http://127.0.0.1:PORTA_NODO_3,http://127.0.0.1:PORTA_NODO_4,http://127.0.0.1:PORTA_NODO_5"

Prepariamo Node.js ed ethers

Inizializziamo il progetto:

npm init -y
npm install ethers

Ora creiamo lo script send-messages.mjs.

Questo script fa una cosa molto pratica: prende 5 wallet, ognuno con saldo finto, e li fa comunicare tra loro. Ogni transazione contiene anche un piccolo messaggio nel campo data.

import { ethers } from "ethers";
import fs from "node:fs/promises";

const rpcUrls = (process.env.RPC_URLS || "").split(",").map((item) => item.trim()).filter(Boolean);

if (rpcUrls.length < 1) {
  throw new Error("Imposta RPC_URLS con almeno un endpoint RPC della rete Kurtosis.");
}

const privateKeys = [
  "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
  "0x59c6995e998f97a5a0044966f094538e4e22c994f3371dcb4d30d8c6d557378c",
  "0x5de4111a0605e5ad348e69c646323a03ca8b7a165bd2b05b5cf558e04e4a2",
  "0x7c8521182943a5eab3e3f2c0987ee1cf9c0e5d86eec6811048f0b5b7bb9a2",
  "0x47e179ec197488433ba80eacc38a093997d63ee5a459f9b825ad8e9d74c7b8"
];

const providers = rpcUrls.map((url) => new ethers.JsonRpcProvider(url));
const wallets = privateKeys.map((key, index) => new ethers.Wallet(key, providers[index % providers.length]));

const log = [];

for (let round = 0; round < 3; round++) {
  for (let index = 0; index < wallets.length; index++) {
    const sender = wallets[index];
    const receiver = wallets[(index + 1) % wallets.length];
    const providerIndex = index % providers.length;
    const message = `nodeai-msg round=${round + 1} from=${index + 1} to=${((index + 1) % wallets.length) + 1}`;

    const tx = await sender.sendTransaction({
      to: receiver.address,
      value: ethers.parseEther("0.001"),
      data: ethers.hexlify(ethers.toUtf8Bytes(message))
    });

    const sentAt = new Date().toISOString();
    const receipt = await tx.wait();

    log.push({
      hash: tx.hash,
      from: sender.address,
      to: receiver.address,
      valueEth: "0.001",
      message,
      rpcUrl: rpcUrls[providerIndex],
      blockNumber: receipt.blockNumber,
      timestamp: sentAt,
      status: receipt.status
    });

    console.log(`[ok] ${tx.hash} ${sender.address} -> ${receiver.address}`);
  }
}

await fs.writeFile("tx-log.json", JSON.stringify(log, null, 2));
console.log(`Salvate ${log.length} transazioni in tx-log.json`);

Lanciamolo:

node send-messages.mjs

Se tutto e’ corretto, vedrai una lista di hash transazione.

Ogni hash e’ una prova: la transazione e’ stata firmata, inviata a un nodo, propagata alla rete e inserita in un blocco.

Guardiamo cosa abbiamo ottenuto

Apriamo tx-log.json.

Dentro troveremo oggetti simili a questo:

{
  "hash": "0x...",
  "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
  "to": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
  "valueEth": "0.001",
  "message": "nodeai-msg round=1 from=1 to=2",
  "rpcUrl": "http://127.0.0.1:xxxxx",
  "blockNumber": 12,
  "timestamp": "2026-06-18T10:15:00.000Z",
  "status": 1
}

Questo file e’ molto piu’ interessante di quanto sembri.

Perche’ contiene la storia del nostro piccolo sistema:

  • chi ha inviato;
  • chi ha ricevuto;
  • quando e’ stata inviata la transazione;
  • in quale blocco e’ entrata;
  • quale messaggio abbiamo scritto nella transazione;
  • quale nodo RPC abbiamo usato come punto di ingresso.

In una blockchain pubblica questi dati possono diventare enormi. Qui invece li abbiamo in piccolo, leggibili, sotto controllo.

Avviamo Ollama in locale

Ora aggiungiamo la parte AI.

Ollama ci permette di usare modelli locali senza mandare i dati fuori dalla macchina. Questo e’ perfetto per un esperimento su traffico blockchain, chiavi pubbliche, indirizzi e timestamp.

Avviamo Ollama con Docker:

docker run -d --name nodeai-ollama -p 11434:11434 -v ollama:/root/.ollama ollama/ollama

Scarichiamo un modello leggero:

docker exec -it nodeai-ollama ollama pull llama3.2

Se hai una macchina meno potente, puoi usare un modello piu’ piccolo. Se hai piu’ risorse, puoi usare un modello piu’ capace.

Verifichiamo che l’API risponda:

curl http://localhost:11434/api/tags

Facciamo analizzare il traffico alla LLM locale

Creiamo analyze-traffic.mjs.

Lo script legge tx-log.json, calcola qualche statistica di base e poi passa tutto a Ollama chiedendo una sintesi tecnica ma leggibile.

import fs from "node:fs/promises";

const model = process.env.OLLAMA_MODEL || "llama3.2";
const ollamaUrl = process.env.OLLAMA_URL || "http://localhost:11434/api/chat";

const raw = await fs.readFile("tx-log.json", "utf8");
const transactions = JSON.parse(raw);

const stats = {
  totalTransactions: transactions.length,
  bySender: {},
  byReceiver: {},
  pairs: {},
  firstTimestamp: null,
  lastTimestamp: null,
  blocks: [...new Set(transactions.map((tx) => tx.blockNumber))].sort((a, b) => a - b)
};

for (const tx of transactions) {
  stats.bySender[tx.from] = (stats.bySender[tx.from] || 0) + 1;
  stats.byReceiver[tx.to] = (stats.byReceiver[tx.to] || 0) + 1;

  const pair = `${tx.from} -> ${tx.to}`;
  stats.pairs[pair] = (stats.pairs[pair] || 0) + 1;

  if (!stats.firstTimestamp || tx.timestamp < stats.firstTimestamp) {
    stats.firstTimestamp = tx.timestamp;
  }

  if (!stats.lastTimestamp || tx.timestamp > stats.lastTimestamp) {
    stats.lastTimestamp = tx.timestamp;
  }
}

const prompt = `
Sei un analista blockchain.
Analizza questo traffico di una rete Ethereum locale.

Devi rispondere in italiano, con tono chiaro e pratico.

Spiega:
- quante transazioni sono state osservate;
- quali chiavi pubbliche inviano piu' transazioni;
- quali chiavi pubbliche ricevono piu' transazioni;
- quali coppie mittente/destinatario comunicano;
- quali timestamp delimitano l'esperimento;
- quali messaggi sono stati scambiati;
- se il traffico sembra bilanciato o concentrato.

Statistiche:
${JSON.stringify(stats, null, 2)}

Transazioni:
${JSON.stringify(transactions, null, 2)}
`;

const response = await fetch(ollamaUrl, {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model,
    stream: false,
    messages: [
      {
        role: "user",
        content: prompt
      }
    ]
  })
});

if (!response.ok) {
  throw new Error(`Errore Ollama: ${response.status} ${await response.text()}`);
}

const result = await response.json();
console.log(result.message.content);

Lanciamo:

node analyze-traffic.mjs

Il risultato sara’ un report naturale, ad esempio:

Sono state osservate 15 transazioni distribuite tra 5 indirizzi.
Ogni indirizzo ha inviato 3 transazioni e ne ha ricevute 3.
Il traffico appare bilanciato, perche' ogni nodo/account partecipa allo scambio.
I messaggi indicano tre round di comunicazione...

Ed ecco il punto: abbiamo usato una LLM locale per leggere dati prodotti da una blockchain locale.

Nessun dato mandato a un servizio esterno. Nessuna API key. Nessun costo di gas. Solo il nostro computer.

Cosa sta succedendo davvero sotto il cofano

Quando eseguiamo send-messages.mjs, lo script firma una transazione con una chiave privata.

La transazione contiene:

  • destinatario;
  • valore;
  • messaggio nel campo data;
  • gas;
  • nonce;
  • firma crittografica.

La transazione viene inviata a uno degli endpoint RPC.

Il nodo che la riceve la mette nella sua mempool, cioe’ una zona temporanea dove restano le transazioni in attesa di essere inserite in un blocco.

Gli altri nodi vengono informati.

Quando arriva il momento di produrre un blocco, la transazione viene inclusa nella catena. Da quel momento possiamo recuperarla tramite hash e verificare che sia nello stesso stato condiviso da tutti i nodi.

Questo e’ il cuore della blockchain: tanti nodi indipendenti che arrivano alla stessa versione della verita’.

Perche’ i messaggi dentro le transazioni sono utili

In Ethereum il campo data puo’ essere usato per chiamare smart contract, ma puo’ anche contenere dati arbitrari.

Nel nostro laboratorio lo usiamo per scrivere messaggi semplici:

nodeai-msg round=1 from=1 to=2

Non e’ un sistema di chat. Non e’ pensato per messaggi privati. Tutto cio’ che mettiamo in una transazione blockchain deve essere considerato pubblico.

Pero’ per un laboratorio e’ utilissimo, perche’ ci permette di vedere la blockchain come sistema di comunicazione tra identita’ crittografiche.

Ogni messaggio ha:

  • un mittente verificabile;
  • un destinatario;
  • un timestamp lato applicazione;
  • un blocco;
  • un hash transazione.

Questi elementi sono la base per costruire sistemi piu’ avanzati: audit trail, notarizzazione, log distribuiti, tracciamento eventi, supply chain, identita’ digitale.

Fermare e pulire tutto

Quando hai finito, puoi fermare la rete:

kurtosis enclave stop nodeai-chain

Per eliminarla:

kurtosis enclave rm -f nodeai-chain

Per fermare Ollama:

docker stop nodeai-ollama
docker rm nodeai-ollama

Se vuoi cancellare anche i modelli scaricati nel volume Docker:

docker volume rm ollama

Errori comuni

Docker non e’ avviato

Se Kurtosis non parte, spesso il problema e’ Docker Desktop spento.

Controlla:

docker ps

Non trovo gli endpoint RPC

Esegui:

kurtosis enclave inspect nodeai-chain

Cerca i servizi execution client e le porte pubblicate verso 127.0.0.1.

Le transazioni falliscono per saldo insufficiente

Controlla che gli indirizzi in prefunded_accounts siano gli stessi derivati dalle chiavi private nello script.

Gli indirizzi usati in questa guida sono account di sviluppo noti, e devono avere ETH finti nel genesis.

Ollama non risponde

Controlla il container:

docker ps
docker logs nodeai-ollama

E verifica:

curl http://localhost:11434/api/tags

Cosa abbiamo imparato

Abbiamo visto una blockchain non come parola da conferenza, ma come sistema vivo.

Abbiamo creato una rete Ethereum locale con Docker, composta da 5 nodi. Abbiamo fatto viaggiare transazioni tra account diversi. Abbiamo inserito messaggi dentro le transazioni. Abbiamo salvato hash, chiavi pubbliche, blocchi e timestamp. Infine abbiamo usato Ollama per analizzare il traffico con una LLM locale.

Questo tipo di laboratorio e’ molto potente perche’ toglie la blockchain dalla nebbia.

La blockchain diventa una cosa che puoi avviare, rompere, osservare, spegnere e ricostruire.

Ed e’ qui che si impara davvero.

Possibili evoluzioni

Da qui puoi andare avanti in diversi modi:

  • aggiungere uno smart contract Solidity;
  • salvare eventi on-chain invece dei messaggi nel campo data;
  • creare una dashboard web per leggere i blocchi;
  • usare Ollama per classificare transazioni sospette;
  • confrontare traffico normale e traffico anomalo;
  • esportare le metriche su Grafana;
  • creare un agente AI che interroga la blockchain locale.

Il passo successivo naturale e’ questo: invece di far analizzare a Ollama solo un file JSON, possiamo collegarlo a un piccolo servizio che osserva i nuovi blocchi in tempo reale.

A quel punto la rete Ethereum locale diventa un laboratorio per agenti AI, auditing, sicurezza e automazione.

Fonti tecniche

Disclaimer personale

Questo articolo nasce anche come raccolta di appunti personali.

Mi serve per riorganizzare le idee, mettere in fila i passaggi tecnici e preparare in modo piu’ chiaro il mio prossimo intervento, dove l’obiettivo sara’ spiegare e dimostrare dal vivo il funzionamento di una blockchain.

Il percorso descritto qui non vuole essere una guida definitiva alla produzione di reti Ethereum, ma un laboratorio ragionato: un modo pratico per osservare nodi, transazioni, messaggi, timestamp e analisi del traffico in un ambiente locale, controllato e a costo zero.

Ringrazio quindi anche questo articolo, in un certo senso, per avermi aiutato a confermare e ordinare meglio i concetti che voglio portare in aula: la blockchain si capisce davvero quando la si vede funzionare.

 

Di R. Andrea Belvedere

Mi occupo di AI applicata, automazione, LLM, RAG e blockchain, con esperienza in contesti enterprise e industriali. Sono formatore tecnico, scrittore SEO e divulgatore scientifico: mi piace spiegare tecnologie complesse in modo semplice, pratico e orientato all’uso reale. Tutti gli Articoli sono elaborati dal sottoscritto sulla base di un progetto sviluppato direttamente dall’autore. Strumenti di intelligenza artificiale sono stati utilizzati come supporto all’analisi della documentazione e alla redazione. Il contenuto è stato verificato e revisionato dall’autore, che ne assume la responsabilità editoriale.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

error: Content is protected !!