[II - IOTA Development]: Configuración y puesta en marcha del nodo Hornet
Tags: iota, hornet, dlt, shimmer
Published: 2023-01-25 23:01:42

Después de ver en el [primer capítulo](https://dev.to/alvarogardev/i-iota-development-preparar-el-entorno-c1) la instalación de todas las herramientas básicas que iremos necesitando durante nuestra aventura, vamos a empezar con la puesta en marcha de un nodo Hornet para tener nuestra red shimmer local.

Hornet, es un software desarrollado por la Fundación IOTA, escrito en Go y es el nodo que implementa la funcionalidad completa de los últimos avances de la red, como es actualmente la actualización `Stardust`.

## Puesta en marcha del nodo Hornet local
Lo primero vamos a crear un espacio de trabajo para todos los ficheros de configuración de nuestra red privada. 
```
~$ mkdir ~/iota-dev/private-network
```
dentro de ese directorio iremos creando todos los ficheros de configuración para poder tener nuestro nodo hornet operativo.

<p>&#8232</p>
#### ./config.json
Ese fichero contiene toda la configuración para nuestro nodo hornet, dejo por aquí el enlace a la [wiki de iota](https://wiki.iota.org/shimmer/hornet/references/configuration/) con todos los parámetros de configuración posibles, también podemos hacer un `hornet -h --full` y obtenemos un listado de todos los parámetros posibles actualizados.
```json
{
  "node": {
    "alias": "ALVAROGAR DEV"
  },
  "protocol": {
    "targetNetworkName": "alvarogar-dev",
    "milestonePublicKeyCount": 2,
    "baseToken": {
      "name": "TestCoin",
      "tickerSymbol": "TEST",
      "unit": "TEST",
      "subunit": "testies",
      "decimals": 6,
      "useMetricPrefix": false
    },
    "publicKeyRanges": [
      {
        "key": "ed3c3f1a319ff4e909cf2771d79fece0ac9bd9fd2ee49ea6c0885c9cb3b1248c",
        "start": 0,
        "end": 0
      },
      {
        "key": "f6752f5f46a53364e2ee9c4d662d762a81efd51010282a75cd6bd03f28ef349c",
        "start": 0,
        "end": 0
      }
    ]
  },
  "participation": {
    "db": {
      "path": "participationdb"
    } 
  },
  "db": {
    "path": "privatedb"
  },
  "p2p": {
    "db": {
      "path": "p2pstore"
    }
  },
  "snapshots": {
    "fullPath": "snapshots/full_snapshot.bin",
    "deltaPath": "snapshots/delta_snapshot.bin",
    "downloadURLs": []
  },
  "restAPI": {
    "publicRoutes": [
      "/health",
      "/api/*"
    ],
    "protectedRoutes": [],
    "pow": {
      "enabled": true
    }
  }
}
```

<p>&#8232</p>
#### ./Dockerfile
Este fichero es el que se encarga de crear la imagen con todos los plugins de hornet, se pueden modificar las versiones de las imágenes usadas para hornet y los plugins inx.
```Dockerfile
FROM iotaledger/hornet:2.0.0-rc.4 AS hornet
FROM iotaledger/inx-indexer:1.0-rc AS indexer
FROM iotaledger/inx-faucet:1.0-rc AS faucet
FROM iotaledger/inx-dashboard:1.0-rc AS dashboard
FROM iotaledger/inx-mqtt:1.0-rc AS mqtt
FROM iotaledger/inx-spammer:1.0-rc AS spammer
FROM iotaledger/inx-coordinator:1.0-rc AS coordinator
FROM iotaledger/inx-poi:1.0-rc AS poi
FROM iotaledger/inx-participation:1.0-rc AS participation

FROM debian:11

# hornet-1 API
EXPOSE 14265/tcp

# hornet-1 dashboard
EXPOSE 8081/tcp

# hornet-1 INX
EXPOSE 9029/tcp

# faucet
EXPOSE 8091/tcp

RUN mkdir /app
WORKDIR /app

# Prepare supervisor
RUN apt update && apt install -y supervisor parallel
RUN mkdir -p /var/log/supervisor
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf


# Copy the inx apps dir into HORNET image
COPY --from=hornet /app /app/hornet-1
COPY --from=hornet /app /app/hornet-2
COPY --from=indexer /app /app/indexer
COPY --from=faucet /app /app/faucet
COPY --from=dashboard /app /app/dashboard
COPY --from=mqtt /app /app/mqtt
COPY --from=spammer /app /app/spammer
COPY --from=coordinator /app /app/coordinator
COPY --from=poi /app /app/poi
COPY --from=participation /app /app/participation

# Overwrite default config
COPY config.json /app/hornet-1/config.json
COPY config.json /app/hornet-2/config.json

# Create snapshots
COPY protocol_parameters.json /app/protocol_parameters.json
RUN mkdir /app/hornet-1/snapshots

RUN /app/hornet-1/hornet tool snap-gen \
	--protocolParametersPath=/app/protocol_parameters.json \
	--mintAddress=tst1qq2kvnu9pqzptkggrpqrvltvagccsh6aj2fkdhla7p3lrsy9dwhdzu5l2ye \
	--outputPath=/app/hornet-1/snapshots/full_snapshot.bin

RUN cp -R /app/hornet-1/snapshots /app/hornet-2/

# Bootstrap network
RUN mkdir /app/coordinator/state
RUN COO_PRV_KEYS=651941eddb3e68cb1f6ef4ef5b04625dcf5c70de1fdc4b1c9eadb2c219c074e0ed3c3f1a319ff4e909cf2771d79fece0ac9bd9fd2ee49ea6c0885c9cb3b1248c,0e324c6ff069f31890d496e9004636fd73d8e8b5bea08ec58a4178ca85462325f6752f5f46a53364e2ee9c4d662d762a81efd51010282a75cd6bd03f28ef349c /app/hornet-1/hornet tool bootstrap-private-tangle \
	--configFile=/app/hornet-1/config.json \
	--snapshotPath=/app/hornet-1/snapshots/full_snapshot.bin \
	--databasePath=/app/hornet-1/privatedb \
	--cooStatePath=/app/coordinator/state/coordinator.state


CMD ["/usr/bin/supervisord"]
```

<p>&#8232</p>
#### ./protocol_parameters.json
Este fichero lo vamos a usar para generar un snapshot para la red privada mediante `hornet tool snap-gen`. Si se ejecuta un nodo Hornet por primera vez, se necesita iniciarlo con una instantánea completa. Hornet la descarga automáticamente de fuentes de confianza. Para este caso vamos a generarla con la herramienta `snap-gen`. [más info](https://wiki.iota.org/hornet/how_tos/managing_a_node/#how-to-work-with-snapshots)
```json
{
    "version": 2,
    "networkName": "alvarogar-dev",
    "bech32HRP": "tst",
    "minPoWScore": 0,
    "belowMaxDepth": 15,
    "rentStructure": {
        "vByteCost": 500,
        "vByteFactorData": 1,
        "vByteFactorKey": 10
    },
    "tokenSupply": "2779530283277761"
}
```

<p>&#8232</p>
##### ./supervisord.conf
Este es el fichero de configuración para `supervisor`, es un gestor de procesos que facilita el manejo de procesos en ejecución por largo tiempo, en este caso lo usamos para gestionar los logs e inicializar hornet y todos sus plugins.
```conf
[unix_http_server]
file=/var/run/supervisor.sock

[supervisord]
logfile=/var/log/supervisor/supervisord.log
logfile_maxbytes=50MB
logfile_backups=10
loglevel=error
pidfile=/var/run/supervisord.pid
user=root
nodaemon=true
minfds=8192
childlogdir=/var/log/supervisor/

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock

[program:hornet-1]
command=/app/hornet-1/hornet -c config.json --p2p.bindMultiAddresses=/ip4/0.0.0.0/tcp/15600 --restAPI.bindAddress=0.0.0.0:14265 --p2p.identityPrivateKey=1f46fad4f538a031d4f87f490f6bca4319dfd0307636a5759a22b5e8874bd608f9156ba976a12918c16a481c38c88a7b5351b769adc30390e93b6c0a63b09b79 --p2p.peers=/ip4/127.0.0.1/tcp/15601/p2p/12D3KooWCKwcTWevoRKa2kEBputeGASvEBuDfRDSbe8t1DWugUmL --p2p.peerAliases=hornet-2 --inx.enabled=true --inx.bindAddress=0.0.0.0:9029 
directory = /app/hornet-1
stdout_logfile=/var/log/supervisor/hornet-1.log
redirect_stderr=true
priority=1

[program:hornet-2]
command=/app/hornet-2/hornet -c config.json --p2p.bindMultiAddresses=/ip4/0.0.0.0/tcp/15601 --restAPI.bindAddress=0.0.0.0:14266 --p2p.identityPrivateKey=a06b288ce7fc3b6f1e716f6f7d72050b53417aae4b305a68883550a3bb28597f254b082515a79391a7f13009b4133851a0c4d48e0e948809c3b46ff3e2500b4f --p2p.peers=/ip4/127.0.0.1/tcp/15600/p2p/12D3KooWSagdVaCrS14GeJhM8CbQr41AW2PiYMgptTyAybCbQuEY --p2p.peerAliases=hornet-1
directory = /app/hornet-2
stdout_logfile=/var/log/supervisor/hornet-2.log
redirect_stderr=true
priority=2

[program:inx-coordinator]
command=/app/coordinator/inx-coordinator --coordinator.stateFilePath=state/coordinator.state --coordinator.interval=1s --coordinator.blockBackups.enabled=false
environment=COO_PRV_KEYS="651941eddb3e68cb1f6ef4ef5b04625dcf5c70de1fdc4b1c9eadb2c219c074e0ed3c3f1a319ff4e909cf2771d79fece0ac9bd9fd2ee49ea6c0885c9cb3b1248c,0e324c6ff069f31890d496e9004636fd73d8e8b5bea08ec58a4178ca85462325f6752f5f46a53364e2ee9c4d662d762a81efd51010282a75cd6bd03f28ef349c"
directory = /app/coordinator
stdout_logfile=/var/log/supervisor/inx-coordinator.log
redirect_stderr=true
priority=3

[program:inx-spammer]
command=/app/spammer/inx-spammer  --spammer.autostart=true --spammer.bpsRateLimit=5
autorestart=true
startsecs=10
directory = /app/spammer
stdout_logfile=/var/log/supervisor/inx-spammer.log
redirect_stderr=true
priority=4

[program:inx-dashboard]
command=/app/dashboard/inx-dashboard --dashboard.bindAddress=0.0.0.0:8081 --dashboard.auth.passwordHash=577eb97f8faf2af47ff957b00827d6bfe9d05b810981e3073dc42553505282c1 --dashboard.auth.passwordSalt=e5d8d0bd3bb9723236177b4713a11580c55b69a51e7055dd11fa1dad3b8f6d6c
autorestart=true
startsecs=10
directory = /app/dashboard
stdout_logfile=/var/log/supervisor/inx-dashboard.log
redirect_stderr=true
priority=5

[program:inx-faucet]
command=/app/faucet/inx-faucet --faucet.bindAddress=0.0.0.0:8091 --faucet.amount=100000000000 --faucet.smallAmount=10000000000 --faucet.maxAddressBalance=200000000000 --faucet.rateLimit.enabled=false
environment=FAUCET_PRV_KEY="887844b1e6bf9ca9e0b57584656add4370dbb49a8cb79e2e3032229f30fd80359e3df559ad0de8e5fa019b9ea46d1ee40879f3f3f74594a3306de9dfd43dcd25"
autorestart=true
startsecs=10
directory = /app/faucet
stdout_logfile=/var/log/supervisor/inx-faucet.log
redirect_stderr=true
priority=6

[program:inx-indexer]
command=/app/indexer/inx-indexer
autorestart=true
startsecs=10
directory = /app/indexer
stdout_logfile=/var/log/supervisor/inx-indexer.log
redirect_stderr=true
priority=7

[program:inx-mqtt]
command=/app/mqtt/inx-mqtt
autorestart=true
startsecs=10
directory = /app/mqtt
stdout_logfile=/var/log/supervisor/inx-mqtt.log
redirect_stderr=true
priority=8

[program:inx-poi]
command=/app/poi/inx-poi
autorestart=true
startsecs=10
directory = /app/poi
stdout_logfile=/var/log/supervisor/inx-poi.log
redirect_stderr=true
priority=9

[program:inx-participation]
command=/app/participation/inx-participation
autorestart=true
startsecs=10
directory = /app/participation
stdout_logfile=/var/log/supervisor/inx-participation.log
redirect_stderr=true
priority=11


# This a workaround to label all the logs to be able to have
# a single log-stream in docker and know from which service it came

[program:hornet-logging]
command=parallel --tagstring "{/.}:" --line-buffer tail -f {} ::: hornet-1.log hornet-2.log inx-coordinator.log
user=root
directory=/var/log/supervisor/
startsecs=10
autostart=true
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true

[program:inx-logging]
command=parallel --tagstring "{/.}:" --line-buffer tail -f {} ::: inx-indexer.log inx-spammer.log inx-faucet.log inx-mqtt.log inx-poi.log inx-participation.log
user=root
directory=/var/log/supervisor/
startsecs=10
autostart=true
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
```

<p>&#8232</p>
##### ./docker-compose.yml
Este es el fichero que `docker compose` va a utilizar para levantar nuestros servicios dockerizados, concretamente nuestra imagen de hornet creada a partir del `Dockerfile` anterior. También exponemos una serie de puertos para poder conectarnos desde fuera del container, concretamente son:
* API 14265/tcp
* Dashboard 8081/tcp
* INX 9029/tcp
* Faucet 8091/tcp

```yml
version: "3.9"
services:
  hornet:
    container_name: hornet
    build:
      dockerfile: Dockerfile
    image: hornet:dev
    stop_grace_period: 5m
    ports:
      - "14265:14265/tcp"
      - "8081:8081/tcp"
      - "8091:8091/tcp"
      - "9029:9029/tcp"
```

<p>&#8232</p>
#### Incializando nuestro nodo hornet
Una vez hemos creado todos esos ficheros dentro de nuestra carpeta `/private-network` y teniendo `docker y docker compose` instalado ejecutamos dentro de ese directorio lo siguiente:

```bash
~$ docker compose up
```

este comando lo que va a hacer es construir la imagen de nuestro hornet y va a levantar un contenedor con esa imagen y toda la configuración que le hemos dicho mediante el fichero `Dockerfile`, podemos comprobar que todo funciona correctamente accediendo al dashboard en `http://localhost:8081` o a la faucet en `http://localhost:8091` o podemos conectar nuestra firefly a través del Hornet API en el host `http://localhost:14265`.
Si hacemos cualquier cambio en cualquiera de los ficheros deberemos parar el contenedor y ejecutar `docker compose build` para volver a crear la imagen con los nuevos cambios.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lahct6nlapglww63c9gy.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nug7dfgpz5lke9i3tng2.png)

Espero que os haya gustado y sobre todo que os haya servido, para el próximo capítulo veremos como crear un cluster de 2 nodos wasp formando un committee y dentro de poco smart contracts!!.

Dejad vuestros comentarios y aprendamos juntos. Gracias!