Cómo construir una API de Node.js para Ethereum Blockchain

Publicado: 2022-03-10
Resumen rápido ↬ En este artículo, John Agbanusi explica cómo puede crear una API de Node.js desde cero creando e implementando una Blockchain de Ethereum para la descentralización. También le muestra un proceso paso a paso para integrar tanto la API como la cadena de bloques en una sola API llamada "API de aplicación descentralizada".

La tecnología Blockchain ha ido en aumento en los últimos diez años y ha dado vida a una buena cantidad de productos y plataformas, como Chainalysis (tecnología financiera), Burstiq (tecnología de la salud), Filament (IoT), Opus (transmisión de música) y Ocular (ciberseguridad).

A partir de estos ejemplos, podemos ver que blockchain atraviesa muchos productos y casos de uso, lo que lo hace muy esencial y útil. En fintech (tecnología financiera), se usa como libros de contabilidad descentralizados para seguridad y transparencia en lugares como Chain, Chainalysis, y también es útil en tecnología de la salud para la seguridad de datos de salud confidenciales en Burstiq y Robomed, sin olvidar la tecnología de medios como Opus. y Audius, que también usan blockchain para la transparencia de regalías y, por lo tanto, obtienen regalías completas.

Ocular usa seguridad que viene con blockchain para la gestión de identidad para sistemas biométricos, mientras que Filament usa registros de blockchain para comunicación cifrada en tiempo real. Esto demuestra cuán esencial se ha vuelto blockchain para nosotros al mejorar nuestras vidas. Pero, ¿qué es exactamente una cadena de bloques?

Una cadena de bloques es una base de datos que se comparte a través de una red de computadoras. Una vez que se ha agregado un registro a la cadena, es bastante difícil cambiarlo. Para asegurarse de que todas las copias de la base de datos sean iguales, la red realiza comprobaciones constantes.

Entonces, ¿por qué necesitamos blockchain? Blockchain es una forma segura de registrar actividades y mantener los datos actualizados mientras mantiene un registro de su historial en comparación con los registros o bases de datos tradicionales donde los ataques, errores y tiempos de inactividad son muy posibles. Nadie puede corromper los datos ni eliminarlos accidentalmente, y usted se beneficia tanto de un registro histórico de datos como de un registro actualizado al instante que no se puede borrar o volver inaccesible debido al tiempo de inactividad de un servidor.

Debido a que toda la cadena de bloques está duplicada en muchas computadoras, cualquier usuario puede ver la cadena de bloques completa. Las transacciones o registros no son procesados ​​por un administrador central, sino por una red de usuarios que trabajan para verificar los datos y lograr un consenso.

Las aplicaciones que utilizan blockchain se denominan dApps (aplicaciones descentralizadas). Mirando a nuestro alrededor hoy, encontraremos principalmente aplicaciones descentralizadas en fintech, pero blockchain va más allá de las finanzas descentralizadas. Tenemos plataformas de salud, plataformas de transmisión/intercambio de música, plataformas de comercio electrónico, plataformas de ciberseguridad e IOT que avanzan hacia aplicaciones descentralizadas (dApps) como se mencionó anteriormente.

Entonces, ¿cuándo tendría sentido considerar el uso de blockchain para nuestras aplicaciones, en lugar de una base de datos o registro estándar?

¡Más después del salto! Continúe leyendo a continuación ↓

Aplicaciones comunes de Blockchain

  • Gestionar y asegurar las relaciones digitales
    Cada vez que desee mantener un registro transparente y a largo plazo de los activos (por ejemplo, para registrar los derechos de propiedad o apartamento), blockchain podría ser la solución ideal. Los 'contratos inteligentes' de Ethereum, en particular, son excelentes para facilitar las relaciones digitales. Con un contrato inteligente, los pagos automatizados pueden liberarse cuando las partes en una transacción acuerdan que se han cumplido sus condiciones.
  • Eliminación de intermediarios/guardianes
    Por ejemplo, la mayoría de los proveedores actualmente tienen que interactuar con los huéspedes a través de una plataforma de agregación centralizada, como Airbnb o Uber (que, a su vez, se lleva una parte de cada transacción). Blockchain podría cambiar todo eso.
    Por ejemplo, TUI está tan convencida del poder de blockchain que es pionera en formas de conectar directamente a hoteleros y clientes. De esa manera, pueden realizar transacciones a través de blockchain de una manera fácil, segura y consistente, en lugar de hacerlo a través de una plataforma central de reservas.
  • Registre transacciones seguras entre socios para garantizar la confianza
    Una base de datos tradicional puede ser buena para registrar transacciones simples entre dos partes, pero cuando las cosas se complican, la cadena de bloques puede ayudar a reducir los cuellos de botella y simplificar las relaciones. Además, la seguridad adicional de un sistema descentralizado hace que blockchain sea ideal para transacciones en general.
    Un ejemplo es la Universidad de Melbourne que comenzó a almacenar sus registros en blockchain. El caso de uso más prometedor de blockchain en la educación superior es transformar el "mantenimiento de registros" de títulos, certificados y diplomas. Esto ahorra una gran cantidad de costos de servidores dedicados para almacenamiento o registros.
  • Mantenimiento de registros de acciones pasadas para aplicaciones donde los datos están en flujo constante
    Blockchain es una forma mejor y más segura de registrar la actividad y mantener los datos actualizados mientras mantiene un registro de su historial. Nadie puede corromper los datos ni eliminarlos accidentalmente, y usted se beneficia de un registro histórico de datos, además de un registro actualizado al instante. Un ejemplo de un buen caso de uso es la cadena de bloques en el comercio electrónico, tanto la cadena de bloques como el comercio electrónico implican transacciones.
    Blockchain hace que estas transacciones sean más seguras y rápidas, mientras que las actividades de comercio electrónico dependen de ellas. La tecnología Blockchain permite a los usuarios compartir y almacenar de forma segura activos digitales tanto de forma automática como manual. Esta tecnología tiene la capacidad de manejar las actividades de los usuarios, como el procesamiento de pagos, búsquedas de productos, compras de productos y atención al cliente. También reduce los gastos de gestión de inventario y procesamiento de pagos.
  • La descentralización hace posible su uso en cualquier lugar
    A diferencia de antes, donde tenemos que restringirnos a una región en particular debido a varias razones, como las políticas de cambio de moneda, las limitaciones de las pasarelas de pago dificultan el acceso a los recursos financieros de muchos países que no están en su región o continente. Con el auge y el poder de la descentralización de blockchain o el sistema peer-to-peer, se vuelve más fácil trabajar con otros países.
    Por ejemplo, una tienda de comercio electrónico en Europa puede tener consumidores en África y no requerir un intermediario para procesar sus solicitudes de pago. Además, estas tecnologías están abriendo puertas para que los minoristas en línea hagan uso de los mercados de consumo en países lejanos con bitcoin, es decir, una criptomoneda.
  • Blockhain es tecnológicamente neutral
    Blockchain funciona con todas y cada una de las pilas de tecnología que utiliza un desarrollador. No es necesario que aprenda Node como desarrollador de Python para usar blockchain o aprender Golang. Esto hace que blockchain sea muy fácil de usar.
    De hecho, podemos usarlo directamente con nuestras aplicaciones front-end en Vue/React con la cadena de bloques actuando como nuestra única base de datos para tareas simples y sin complicaciones y casos de uso como cargar datos u obtener hashes para mostrar registros para nuestros usuarios, o crear juegos front-end como casino. juegos y juegos de apuestas (en los que se necesita mucha confianza). Además, con el poder de web3, podemos almacenar datos en la cadena directamente.

Ahora, hemos visto una gran cantidad de ventajas de usar blockchain, pero ¿cuándo no deberíamos molestarnos en usar blockchain?

Desventajas de la cadena de bloques

  • Velocidad reducida para transacciones digitales
    Las cadenas de bloques requieren enormes cantidades de potencia informática, lo que tiende a reducir la velocidad de las transacciones digitales, aunque existen soluciones alternativas, es recomendable utilizar bases de datos centralizadas cuando se necesitan transacciones de alta velocidad en milisegundos.
  • Inmutabilidad de datos
    La inmutabilidad de los datos siempre ha sido una de las mayores desventajas de la cadena de bloques. Está claro que múltiples sistemas se benefician de él, incluida la cadena de suministro, los sistemas financieros, etc. Sin embargo, adolece del hecho de que una vez que se escriben los datos, no se pueden eliminar. Toda persona en la tierra tiene derecho a la privacidad. Sin embargo, si la misma persona utiliza una plataforma digital que funciona con tecnología blockchain, no podrá eliminar su rastro del sistema cuando no lo quiera allí. En palabras simples, no hay forma de que pueda eliminar su rastro, dejando los derechos de privacidad en pedazos.
  • Requiere Experiencia Conocimiento
    Implementar y administrar un proyecto de blockchain es difícil. Se requiere un conocimiento profundo para pasar por todo el proceso. Es por eso que es difícil encontrar especialistas o expertos en blockchain porque se necesita mucho tiempo y esfuerzo para capacitar a un experto en blockchain. Por lo tanto, este artículo es un buen lugar para comenzar y una buena guía si ya ha comenzado.
  • interoperabilidad
    Múltiples redes de blockchain que trabajan arduamente para resolver el problema del libro mayor distribuido hacen que sea difícil relacionarlas o integrarlas entre sí. Esto dificulta la comunicación entre diferentes cadenas.
  • Integración de aplicaciones heredadas
    Muchas empresas y aplicaciones aún utilizan arquitectura y sistemas heredados; La adopción de la tecnología blockchain requiere una revisión completa de estos sistemas que, debo decir, no es factible para muchos de ellos.

Blockchain sigue evolucionando y madurando todo el tiempo, así que no se sorprenda si estos contras mencionados hoy se transforman en ventajas más adelante. Bitcoin, que es una criptomoneda, es un ejemplo popular de una cadena de bloques, una cadena de bloques popular que ha ido en aumento, aparte de la criptomoneda bitcoin, es la cadena de bloques Ethereum. Bitcoin se centra en las criptomonedas, mientras que Ethereum se centra más en los contratos inteligentes, que han sido la principal fuerza impulsora de las nuevas plataformas tecnológicas.

Lectura recomendada : Bitcoin vs. Ethereum: ¿Cuál es la diferencia?

Comencemos a construir nuestra API

Con una sólida comprensión de la cadena de bloques, ahora veamos cómo construir una cadena de bloques de Ethereum e integrarla en una API estándar en Node.js. El objetivo final es obtener una buena comprensión de cómo se construyen las plataformas dApps y Blockchain.

La mayoría de las dApps tienen una arquitectura y una estructura similares. Básicamente, tenemos un usuario que interactúa con la interfaz de dApp, ya sea web o móvil, que luego interactúa con las API de back-end. El backend, entonces, a pedido, interactúa con los contratos inteligentes o la cadena de bloques a través de nodos públicos; estos ejecutan aplicaciones Node.js o el backend usa blockchain al ejecutar directamente el software Node.js. Todavía hay muchas cosas entre estos procesos, desde elegir crear una aplicación completamente descentralizada o una aplicación semidescentralizada hasta elegir qué debería descentralizarse y cómo almacenar claves privadas de manera segura.

Lectura recomendada : Arquitectura de aplicaciones descentralizadas: back-end, seguridad y patrones de diseño

Cosas que debemos saber primero

Para este tutorial, vamos a intentar construir el backend de una aplicación de tienda de música descentralizada que utiliza el poder de la cadena de bloques de Ethereum para almacenar música y compartirla para descargas o transmisión.

La estructura básica de la aplicación que estamos tratando de construir tiene tres partes:

  1. Autenticación , que se realiza por correo electrónico; por supuesto, necesitamos agregar una contraseña cifrada a la aplicación.
  2. El almacenamiento de datos , con los datos de música, se almacena primero en ipfs y la dirección de almacenamiento se almacena en la cadena de bloques para su recuperación.
  3. Recuperación , pudiendo cualquier usuario autenticado acceder a los datos almacenados en nuestra plataforma y utilizarlos.

Estaremos construyendo esto con Node.js, pero también puedes construir con Python o cualquier otro lenguaje de programación. También veremos cómo almacenar datos de medios en IPFS, obtener la dirección y escribir funciones para almacenar esta dirección, y recuperar esta dirección de una cadena de bloques con el lenguaje de programación Solidity.

Aquí hay algunas herramientas que debemos tener a nuestra disposición para construir o trabajar con Ethereum y Node.js.

  • Nodo.js
    El primer requisito es una aplicación Node. Estamos tratando de crear una aplicación Node.js, por lo que necesitamos un compilador. Asegúrese de tener instalado Node.js y descargue el último binario de soporte a largo plazo ( LTS ).
  • Suite Trufa
    Truffle es un entorno de prueba y desarrollo de contratos, así como una canalización de activos para la cadena de bloques de Ethereum. Proporciona un entorno para compilar, canalizar y ejecutar scripts. Una vez que esté hablando sobre el desarrollo de blockchain, Truffle es una parada popular para visitar. Echa un vistazo a Truffle Suite en Truffle Suite: Sweet Tools para contratos inteligentes.
  • CLI de Ganache
    Otra herramienta que funciona bien con Truffle es Ganache-CLI. Está construido y mantenido por el equipo de Truffle Suite. Después de construir y compilar, necesita un emulador para desarrollar y ejecutar aplicaciones de cadena de bloques y luego implementar contratos inteligentes para su uso. Ganache le facilita implementar un contrato en un emulador sin usar dinero real para el costo de transacción, cuentas reciclables y mucho más. Lea más sobre Ganache CLI en Ganache CLI y Ganache.
  • remezclar
    Remix es como una alternativa a Ganache, pero también viene con una GUI para ayudar a navegar en la implementación y prueba de los contratos inteligentes de Ethereum. Puede obtener más información al respecto en Remix: Ethereum IDE y comunidad. Todo lo que tiene que hacer es visitar https://remix.ethereum.org y usar la GUI para escribir e implementar contratos inteligentes.
  • Web3
    Web3 es una colección de bibliotecas que le permite interactuar con un nodo Ethereum. Estos pueden ser nodos locales o remotos del contrato a través de HTTP, IPC o Web Sockets. Introducción a Web3.js · Ethereum Blockchain Developer Crash Course es un buen lugar para aprender un poco sobre Web3.
  • IPFS
    Un protocolo central que se utiliza en la creación de dApps. El Sistema de archivos interplanetarios (IPFS) es un protocolo y una red de igual a igual para almacenar y compartir datos en un sistema de archivos distribuido. IPFS Powers the Distributed Web explica más sobre IPFS y cómo se usa normalmente.

Creación de una API de back-end desde cero

Entonces, primero tenemos que crear un backend para usar, y estamos usando Node.js. Cuando queramos crear una nueva API de Node.js, lo primero que haremos será inicializar un paquete npm. Como probablemente sepa, npm significa Node Package Manager y viene preempaquetado con el binario Node.js. Entonces creamos una nueva carpeta y la llamamos "blockchain-music" . Abrimos la terminal en ese directorio de carpetas, y luego ejecutamos el siguiente comando:

 $ npm init -y && touch server.js routes.js

Esto inicia el proyecto con un archivo package.json y responde afirmativamente a todas las solicitudes. Luego también creamos un archivo server.js y un archivo route.js para escribir las funciones de routes en la API.

Después de todo esto, deberá instalar los paquetes que necesitamos para que nuestra compilación sea fácil y directa. Este proceso es continuo, es decir, puede instalar un paquete en cualquier momento durante el desarrollo de su proyecto.

Instalemos los más importantes que necesitamos ahora mismo:

  • Express.js
  • @trufa/contrato
  • Trufa.js
  • web3.js
  • dotenv
  • short-id
  • MongoDB
  • nodemonio

También deberá instalar Truffle.js globalmente , para que pueda usarlo en cualquier lugar de su entorno local. Si desea instalarlos todos a la vez, ejecute el siguiente código en su Terminal:

 $ npm install nodemon truffle-contract dotenv mongodb shortid express web3 --save && npm install truffle -g

El indicador --save es para guardar el nombre del paquete en el archivo package.json . El indicador -g es para almacenar este paquete en particular globalmente, para que podamos usarlo en cualquier proyecto en el que vayamos a trabajar.

Luego creamos un archivo .env donde podemos almacenar nuestro URI secreto de la base de datos MongoDB para su uso. Lo hacemos ejecutando touch.env en la Terminal. Si aún no tiene una cuenta de base de datos con MongoDB, primero comience con la página de MongoDB.

El paquete dotenv exporta nuestra variable almacenada al entorno de proceso de Node.js. Asegúrese de no enviar el archivo .env al ingresar a repositorios públicos para evitar la filtración de sus contraseñas y datos privados.

A continuación, debemos agregar secuencias de comandos para las fases de compilación y desarrollo de nuestro proyecto en nuestro archivo package.json . Actualmente nuestro paquete.json se ve así:

 { "name": "test", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1", "socket.io": "^2.3.0", "truffle-contract": "^4.0.31", "web3": "^1.3.0" } }

Luego agregaremos una secuencia de comandos de inicio al archivo package.json para usar el servidor de nodemon para que cada vez que hagamos un cambio reinicie el servidor en sí, y una secuencia de comandos de compilación que use el servidor de nodos directamente, podría verse así:

 { "name": "test", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon server.js", "build": "node server.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1", "socket.io": "^2.3.0", "truffle-contract": "^4.0.31", "web3": "^1.3.0" } }

Luego, tenemos que inicializar Truffle para usar en nuestro contrato inteligente usando el paquete Truffle que instalamos globalmente anteriormente. En la misma carpeta de nuestros proyectos, ejecutamos el siguiente comando a continuación en nuestra terminal:

 $ truffle init

Luego podemos comenzar a escribir nuestro código en nuestro archivo server.js . Nuevamente, estamos tratando de crear una aplicación de tienda de música descentralizada simple, donde los clientes pueden cargar música para que todos los demás usuarios accedan y la escuchen.

Nuestro server.js debe estar limpio para facilitar el acoplamiento y desacoplamiento de los componentes, por lo que las rutas y otras funcionalidades se colocarán en otros archivos como las rutas.js . Nuestro ejemplo server.js podría ser:

 require('dotenv').config(); const express= require('express') const app =express() const routes = require('./routes') const Web3 = require('web3'); const mongodb = require('mongodb').MongoClient const contract = require('truffle-contract'); app.use(express.json()) mongodb.connect(process.env.DB,{ useUnifiedTopology: true },(err,client)=>{ const db =client.db('Cluster0') //home routes(app,db) app.listen(process.env.PORT || 8082, () => { console.log('listening on port 8082'); }) })

Básicamente, arriba importamos las bibliotecas que necesitamos con require , luego agregamos un middleware que permite el uso de JSON en nuestra API usando app.use , luego nos conectamos a nuestra base de datos MongoDB y obtenemos el acceso a la base de datos, y luego especificamos qué clúster de base de datos estamos tratando de acceder (para este tutorial es "Cluster0" ). Después de esto, llamamos a la función y la importamos desde el archivo de rutas . Finalmente, escuchamos cualquier intento de conexión en el puerto 8082 .

Este archivo server.js es solo un barebone para iniciar la aplicación. Tenga en cuenta que importamos rutas.js . Este archivo contendrá los puntos finales de ruta para nuestra API. También importamos los paquetes que necesitábamos usar en el archivo server.js y los inicializamos.

Vamos a crear cinco puntos finales para el consumo de los usuarios:

  1. Punto final de registro para registrar usuarios solo por correo electrónico. Idealmente, lo haríamos con un correo electrónico y una contraseña, pero como solo queremos identificar a cada usuario, no vamos a aventurarnos en la seguridad de las contraseñas y el hashing por la brevedad de este tutorial.
     POST /register Requirements: email
  2. Punto final de inicio de sesión para usuarios por correo electrónico.
     POST /login Requirements: email
  3. Punto final de carga para usuarios: la API que obtiene los datos del archivo de música. La interfaz convertirá los archivos MP3/WAV en un búfer de audio y enviará ese búfer a la API.
     POST /upload Requirements: name, title of music, music file buffer or URL stored
  4. Punto final de acceso que proporcionará los datos del búfer de música a cualquier usuario registrado que lo solicite y registrará quién accedió.
     GET /access/{email}/{id} Requirements: email, id
  5. También queremos brindar acceso a toda la biblioteca de música y devolver los resultados a un usuario registrado.
     GET /access/{email} Requirements: email

Luego escribimos nuestras funciones de ruta en nuestro archivo route.js . Utilizamos las funciones de almacenamiento y recuperación de la base de datos, y luego nos aseguramos de exportar la función de ruta al final del archivo para que sea posible importarlo en otro archivo o carpeta.

 const shortid = require('short-id') function routes(app, db){ app.post('/register', (req,res)=>{ let email = req.body.email let idd = shortid.generate() if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.status(400).json({"status":"Failed", "reason":"Already registered"}) }else{ db.insertOne({email}) res.json({"status":"success","id":idd}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/login', (req,res)=>{ let email = req.body.email if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.json({"status":"success","id":doc.id}) }else{ res.status(400).json({"status":"Failed", "reason":"Not recognised"}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/upload', (req,res)=>{ let buffer = req.body.buffer let name = req.body.name let title = req.body.title if(buffer && title){ }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.get('/access/:email/:id', (req,res)=>{ if(req.params.id && req.params.email){ }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) } module.exports = routes

Dentro de esta función de route , tenemos muchas otras funciones llamadas dentro de los db de la app y de la base de datos. Estas son las funciones de punto final de la API que permiten a los usuarios especificar un punto final en la URL. En última instancia, elegimos una de estas funciones para ejecutarla y proporcionar resultados como respuesta a las solicitudes entrantes.

Tenemos cuatro funciones principales de punto final:

  1. get : para leer operaciones de registro
  2. post : para crear operaciones de registro
  3. put : para actualizar operaciones de registro
  4. delete : para eliminar operaciones de registro

En esta función de routes , usamos las operaciones get y post . Usamos post para operaciones de registro, inicio de sesión y carga, y get para acceder a las operaciones de datos. Para obtener un poco más de explicación al respecto, puede consultar el artículo de Jamie Corkhill sobre "Cómo comenzar con Node: una introducción a las API, HTTP y ES6 + JavaScript".

En el código anterior, también podemos ver algunas operaciones de base de datos como en la ruta de registro . Almacenamos el correo electrónico de un nuevo usuario con db.createa y buscamos el correo electrónico en la función de inicio de sesión con db.findOne . Ahora, antes de que podamos hacerlo todo, debemos nombrar una colección o tabla con el método db.collection . Eso es exactamente lo que cubriremos a continuación.

Nota : Para obtener más información sobre las operaciones de la base de datos en MongoDB, consulte la documentación de Métodos de shell de mongo.

Construyendo un contrato inteligente de cadena de bloques simple con Solidity

Ahora vamos a escribir un contrato Blockchain en Solidity (ese es el lenguaje en el que están escritos los contratos inteligentes) para simplemente almacenar nuestros datos y recuperarlos cuando los necesitemos. Los datos que queremos almacenar son los datos del archivo de música, lo que significa que tenemos que cargar la música en IPFS y luego almacenar la dirección del búfer en una cadena de bloques.

Primero, creamos un nuevo archivo en la carpeta de contratos y lo llamamos Inbox.sol . Para escribir un contrato inteligente, es útil tener una buena comprensión de Solidity, pero no es difícil ya que es similar a JavaScript.

Nota : si está interesado en obtener más información sobre Solidity, he agregado algunos recursos al final del artículo para que pueda comenzar.

 pragma solidity ^0.5.0; contract Inbox{ //Structure mapping (string=>string) public ipfsInbox; //Events event ipfsSent(string _ipfsHash, string _address); event inboxResponse(string response); //Modifiers modifier notFull (string memory _string) { bytes memory stringTest = bytes(_string); require(stringTest.length==0); _; } // An empty constructor that creates an instance of the conteact constructor() public{} //takes in receiver's address and IPFS hash. Places the IPFSadress in the receiver's inbox function sendIPFS(string memory _address, string memory _ipfsHash) notFull(ipfsInbox[_address]) public{ ipfsInbox[_address] = _ipfsHash; emit ipfsSent(_ipfsHash, _address); } //retrieves hash function getHash(string memory _address) public view returns(string memory) { string memory ipfs_hash=ipfsInbox[_address]; //emit inboxResponse(ipfs_hash); return ipfs_hash; } }

En nuestro contrato, tenemos dos funciones principales: las funciones sendIPFS y getHash . Antes de hablar de las funciones, podemos ver que primero tuvimos que definir un contrato llamado Inbox de entrada. Dentro de esta clase, tenemos estructuras utilizadas en el objeto ipfsInbox (primero eventos, luego modificadores).

Después de definir las estructuras y los eventos, debemos inicializar el contrato llamando a la función constructor . Luego definimos tres funciones. (La función checkInbox se usó en la prueba para probar los resultados).

sendIPFS es donde el usuario ingresa el identificador y la dirección hash, después de lo cual se almacena en la cadena de bloques. La función getHash recupera la dirección hash cuando se le proporciona el identificador. Nuevamente, la lógica detrás de esto es que, en última instancia, queremos almacenar la música en IPFS. Para probar cómo funciona, puede acceder a un IDE de Remix, copiar, pegar y probar su contrato, así como depurar cualquier error y volver a ejecutar (¡con suerte no será necesario!).

Después de probar que nuestro código funciona correctamente en el remix, pasemos a compilarlo localmente con la suite Truffle. Pero primero, debemos hacer algunos cambios en nuestros archivos y configurar nuestro emulador usando ganache-cli :

Primero, ganache-cli . En el mismo directorio, ejecute el siguiente comando en su terminal:

 $ npm install ganache-cli -g

Luego abramos otra Terminal y ejecutemos otro comando en la misma carpeta:

 $ ganache-cli

Esto inicia el emulador para que nuestro contrato de blockchain se conecte y funcione. Minimice la Terminal y continúe con la otra Terminal que ha estado usando.

Ahora vaya al archivo truffle.js si está usando Linux/Mac OS o truffle-config.js en Windows, y modifique este archivo para que se vea así:

 const path = require("path"); module.exports = { // to customize your Truffle configuration! contracts_build_directory: path.join(__dirname, "/build"), networks: { development: { host: "127.0.0.1", port: 8545, network_id: "*" //Match any network id } } };

Básicamente, lo que hicimos fue agregar la ruta de la carpeta de compilación donde el contrato inteligente se convierte en archivos JSON. Luego, también especificamos la red que Truffle debería usar para la migración.

Luego, también en la carpeta de migraciones , cree un nuevo archivo llamado 2_migrate_inbox.js y agregue el siguiente código dentro de los archivos:

 var IPFSInbox = artifacts.require("./Inbox.sol"); module.exports = function(deployer) { deployer.deploy(IPFSInbox); };

Lo hicimos para obtener el archivo del contrato e implementarlo automáticamente en un JSON, utilizando la función de deployer durante la migración de Truffle.

Después de los cambios anteriores ejecutamos:

 $ truffle compile

Deberíamos ver algunos mensajes al final que muestran una compilación exitosa, como:

 > Compiled successfully using: - solc: 0.5.16+commit.9c3226ce.Emscripten.clang

A continuación, migramos nuestro contrato ejecutando:

 $ truffle migrate

Una vez que hayamos migrado con éxito nuestros contratos, deberíamos tener algo como esto al final:

 Summary ======= > Total deployments: 1 > Final cost: 0.00973432 ETH

¡Y ya casi terminamos! Construimos nuestra API con Node.js y también configuramos y creamos nuestro contrato inteligente.

También deberíamos escribir pruebas para nuestro contrato para probar el comportamiento de nuestro contrato y asegurarnos de que es el comportamiento deseado. Las pruebas generalmente se escriben y se colocan en la carpeta de test . Una prueba de ejemplo escrita en un archivo llamado InboxTest.js creado en la carpeta de prueba es:

 const IPFSInbox = artifacts.require("./Inbox.sol") contract("IPFSInbox", accounts =>{ it("emit event when you send a ipfs address", async()=>{ //ait for the contract const ipfsInbox = await IPFSInbox.deployed() //set a variable to false and get event listener eventEmitted = false //var event = () await ipfsInbox.ipfsSent((err,res)=>{ eventEmitted=true }) //call the contract function which sends the ipfs address await ipfsInbox.sendIPFS(accounts[1], "sampleAddress", {from: accounts[0]}) assert.equal(eventEmitted, true, "sending an IPFS request does not emit an event") }) })

Así que ejecutamos nuestra prueba ejecutando lo siguiente:

 $ truffle test

Prueba nuestro contrato con los archivos en la carpeta de prueba y muestra el número de pruebas aprobadas y fallidas. Para este tutorial, deberíamos obtener:

 $ truffle test Using network 'development'. Compiling your contracts... =========================== > Compiling .\contracts\Inbox.sol > Artifacts written to C:\Users\Ademola\AppData\Local\Temp\test--2508-n0vZ513BXz4N > Compiled successfully using: — solc: 0.5.16+commit.9c3226ce.Emscripten.clang Contract: IPFSInbox √ emit event when you send an ipfs address (373ms) 1 passing (612ms)

Integración del contrato inteligente a la API de back-end usando Web3

La mayoría de las veces, cuando ve tutoriales, ve aplicaciones descentralizadas creadas para integrar la interfaz directamente a la cadena de bloques. Pero hay momentos en los que también se necesita la integración con el backend, por ejemplo, cuando se usan API y servicios de backend de terceros, o cuando se usa blockchain para crear un CMS.

El uso de Web3 es muy importante para esta causa, ya que nos ayuda a acceder a nodos de Ethereum remotos o locales y usarlos en nuestras aplicaciones. Antes de continuar, analizaremos los nodos locales y remotos de Ethereum. Los nodos locales son los nodos implementados en nuestro sistema con emuladores como ganache-cli pero un nodo remoto es uno que se implementa en grifos/plataformas en línea como ropsten o rinkeby . Para profundizar más, puede seguir un tutorial sobre cómo implementar en la guía de 5 minutos de ropsten para implementar contratos inteligentes con Truffle y Ropsten o puede usar el proveedor de billetera truffle e implementar a través de Una forma más fácil de implementar sus contratos inteligentes.

Estamos usando ganache-cli en este tutorial, pero si estábamos implementando en ropsten, deberíamos haber copiado o almacenado nuestra dirección de contrato en algún lugar como en nuestro archivo .env, luego pasar a actualizar el archivo server.js , importar web3, importar el contrato migrado y configurar una instancia de Web3.

 require('dotenv').config(); const express= require('express') const app =express() const routes = require('./routes') const Web3 = require('web3'); const mongodb = require('mongodb').MongoClient const contract = require('truffle-contract'); const artifacts = require('./build/Inbox.json'); app.use(express.json()) if (typeof web3 !== 'undefined') { var web3 = new Web3(web3.currentProvider) } else { var web3 = new Web3(new Web3.providers.HttpProvider('https://localhost:8545')) } const LMS = contract(artifacts) LMS.setProvider(web3.currentProvider) mongodb.connect(process.env.DB,{ useUnifiedTopology: true }, async(err,client)=>{ const db =client.db('Cluster0') const accounts = await web3.eth.getAccounts(); const lms = await LMS.deployed(); //const lms = LMS.at(contract_address) for remote nodes deployed on ropsten or rinkeby routes(app,db, lms, accounts) app.listen(process.env.PORT || 8082, () => { console.log('listening on port '+ (process.env.PORT || 8082)); }) })

En el archivo server.js , verificamos si la instancia web3 ya está inicializada. Si no, lo inicializamos en el puerto de red que definimos anteriormente ( 8545 ). Luego creamos un contrato basado en el archivo JSON migrado y el paquete truffle-contract , y establecemos el proveedor del contrato en el proveedor de la instancia Web3 que ya debe haberse inicializado.

Luego obtenemos cuentas por web3.eth.getAccounts . Para la etapa de desarrollo, llamamos a la función implementada en nuestra clase de contrato que le pide a ganache-cli , que aún se está ejecutando, que nos proporcione una dirección de contrato para usar. Pero si ya implementamos nuestro contrato en un nodo remoto, llamamos a una función ingresando la dirección como argumento. La función de muestra se comenta debajo de la variable lms definida en nuestro código anterior. Luego llamamos a la función de routes ingresando la instancia de la aplicación, la instancia de la base de datos, la instancia del contrato ( lms ) y los datos de las cuentas como argumentos. Finalmente, escuchamos solicitudes en el puerto 8082 .

Además, a estas alturas, deberíamos haber instalado el paquete MongoDB, porque lo estamos usando en nuestra API como nuestra base de datos. Una vez que tenemos eso, pasamos a la página de rutas donde usamos los métodos definidos en el contrato para realizar tareas como guardar y recuperar los datos de música.

Al final, nuestro route.js debería verse así:

 const shortid = require('short-id') const IPFS =require('ipfs-api'); const ipfs = IPFS({ host: 'ipfs.infura.io', port: 5001,protocol: 'https' }); function routes(app, dbe, lms, accounts){ let db= dbe.collection('music-users') let music = dbe.collection('music-store') app.post('/register', (req,res)=>{ let email = req.body.email let idd = shortid.generate() if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.status(400).json({"status":"Failed", "reason":"Already registered"}) }else{ db.insertOne({email}) res.json({"status":"success","id":idd}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/login', (req,res)=>{ let email = req.body.email if(email){ db.findOne({email}, (err, doc)=>{ if(doc){ res.json({"status":"success","id":doc.id}) }else{ res.status(400).json({"status":"Failed", "reason":"Not recognised"}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.post('/upload', async (req,res)=>{ let buffer = req.body.buffer let name = req.body.name let title = req.body.title let id = shortid.generate() + shortid.generate() if(buffer && title){ let ipfsHash = await ipfs.add(buffer) let hash = ipfsHash[0].hash lms.sendIPFS(id, hash, {from: accounts[0]}) .then((_hash, _address)=>{ music.insertOne({id,hash, title,name}) res.json({"status":"success", id}) }) .catch(err=>{ res.status(500).json({"status":"Failed", "reason":"Upload error occured"}) }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.get('/access/:email', (req,res)=>{ if(req.params.email){ db.findOne({email: req.body.email}, (err,doc)=>{ if(doc){ let data = music.find().toArray() res.json({"status":"success", data}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) app.get('/access/:email/:id', (req,res)=>{ let id = req.params.id if(req.params.id && req.params.email){ db.findOne({email:req.body.email},(err,doc)=>{ if(doc){ lms.getHash(id, {from: accounts[0]}) .then(async(hash)=>{ let data = await ipfs.files.get(hash) res.json({"status":"success", data: data.content}) }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) }else{ res.status(400).json({"status":"Failed", "reason":"wrong input"}) } }) } module.exports = routes

At the beginning of the routes file, we imported the short-id package and ipfs-http-client and then initialized IPFS with the HTTP client using the backend URL ipfs.infura.io and port 5001 . This allowed us to use the IPFS methods to upload and retrieve data from IPFS (check out more here).

In the upload route, we save the audio buffer to IPFS which is better compared to just storing it on the blockchain for anyone registered or unregistered to use. Then we saved the address of the buffer in the blockchain by generating an ID and using it as an identifier in the sendIFPS function. Finally, then we save all the other data associated with the music file to our database. We should not forget to update our argument in the routes function since we changed it in the server.js file.

In the access route using id , we then retrieve our data by getting the id from the request, using the id to access the IPFS hash address, and then access the audio buffer using the address. But this requires authentication of a user by email which is done before anything else.

Phew, we're done ! Right now we have an API that can receive requests from users, access a database, and communicate to a node that has the software running on them. We shouldn't forget that we have to export our function with module.exports though!

As we have noticed, our app is a decentralized app . However, it's not fully decentralized as we only stored our address data on the blockchain and every other piece of data was stored securely in a centralized database which is the basis for semi-dApps . So the consumption of data can be done directly via request or using a frontend application in JavaScript to send fetch requests.

Our music store backend app can now safely store music data and provide access to anyone who needs to access it, provided it is a registered user. Using blockchain for music sharing makes it cheaper to store music data while focusing on connecting artists directly with users, and perhaps it could help them generate revenue that way. This wouldn't require a middleman that uses royalty; instead, all of the revenue would go to the artist as users request their music to either download or stream. A good example of a music streaming application that uses blockchain just like this is Opus OPUS: Decentralized music sharing platform. However, there are also a few others like Musicoin, Audius, and Resonate.

¿Qué sigue?

The final thing after coding is to start our server by running npm run start or npm run build and test our backend endpoints on either the browser or with Postman. After running and testing our API we could add more features to our backend and blockchain smart contract. If you'd like to get more guidance on that, please check the further reading section for more articles.

It's worth mentioning that it is critical to write unit and integration tests for our API to ensure correct and desirable behaviors. Once we have all of that done, we can deploy our application on the cloud for public use. This can be done on its own with or without adding a frontend (microservices) on Heroku, GCP, or AWS for public use. Happy coding!

Note : You can always check my repo for reference. Also, please note that the .env file containing the MongoDB database URI is included for security reasons.

Further Reading And Related Resources

  • “How to Build Ethereum Dapp with React.js: Complete Step-By-Step Guide,” Gregory McCubbin
  • “Ethereum + IPFS + React DApp Tutorial Pt. 1,” Alexander Ma
  • “Ethereum Development with Go,” Miguel Mota
  • “Create your first Ethereum dAPP with Web3 and Vue.JS (Part 1),” Nico Vergauwen
  • “Deploy a Smart Contract on Ethereum with Python, Truffle and web3py,” Gabriel Saldanha
  • “Why Use Blockchain Technology?,” Bernard Marr
  • “How To Build Your Own Blockchain Using Node.js,” DevTeam.Space
  • “How To Build A Blockchain App With Ethereum, Web3.js & Solidity Smart Contracts,” Gregory McCubbin
  • “How To Build A Simple Cryptocurrency Blockchain In Node.js,” Alfrick Opidi
  • “How Blockchain Technology Is Going To Revolutionize Ecommerce,” Sergii Shanin
  • “4 Ways Blockchain Will Transform Higher Education — Smarter With Gartner,” Susan Moore
  • “How To Learn Solidity: The Ultimate Ethereum Coding Tutorial,” Ryan Molecke
  • “Developing Ethereum Smart Contracts For Beginners,” Coursetro
  • “Learn about Ethereum,” Ethereum official site