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

Las aplicaciones descentralizadas, o ÐApps, requieren un diseño de sistema especial para lograr una alta seguridad y confiabilidad. En este artículo voy a cubrir varios principios fundamentales de cómo diseñar e implementar adecuadamente back-end y contratos inteligentes para aplicaciones descentralizadas, tomando Ethereum como un ejemplo principal, aunque gran parte se aplicaría a EOS, Tron y otras plataformas de datos descentralizadas.

Puntos destacados del artículo :

  • Cómo almacenar claves privadas en el back-end sin preocupaciones de seguridad
  • Cómo diseñar correctamente contratos inteligentes y qué "descentralizar"
  • Ejemplos de arquitectura de aplicaciones descentralizadas y semi-centralizadas
  • Cómo lidiar con cosas de bajo nivel como carga de red y fallas

Va a ser grande, ¡hagámoslo!

Programas descentralizados y Blockchain

A pesar del hecho de que blockchain enfrenta muchas dificultades de adopción y regulación hoy en día, es un tipo de tecnología que llegó para quedarse, ya sea blockchain, hashgraph, tempo o cualquier otra tecnología de contabilidad distribuida que esté por venir, independientemente del algoritmo.

El principal valor que aportan blockchain y otras tecnologías similares se puede generalizar de la siguiente manera: permiten a las personas escribir y ejecutar programas que, prácticamente, no se pueden cambiar después de la creación ni manipular durante la ejecución. En otras palabras, estos programas siempre se ejecutan como se diseñaron y ninguna parte puede influir en su comportamiento.

Esta definición es válida para muchas criptomonedas que existen en la actualidad si las consideramos como programas que definen cómo se pueden transferir las monedas de un lado a otro. Esto también explica por qué las criptomonedas y muchos tipos de tokens tienen un valor real: no pueden generarse de la nada mediante sus “programas subyacentes” definidos.

Las plataformas Ethereum / EOS / Tron /…, a diferencia de Bitcoin, implementan una capa de programa más compleja, que a su vez implementa el entorno de ejecución que permite a cualquiera escribir sus propios programas descentralizados en la parte superior de la plataforma. Estos programas definidos por el usuario siempre se ejecutan según lo diseñado, sin excepciones, y su seguridad está garantizada por la plataforma.

Aplicaciones descentralizadas

Estos programas seguros e inmutables que se ejecutan en una red descentralizada en combinación con las tecnologías tradicionales de front-end y back-end son lo que hoy llamamos aplicaciones descentralizadas (ÐApps). A través de algunos de ellos pueden ser semi-centralizados, una parte importante de las actividades en la aplicación verdaderamente descentralizada debería ocurrir fuera del control de una parte central.

Para imaginar lo que hoy llamamos aplicaciones descentralizadas, tome cualquier recurso web centralizado existente como _YouTube_ o _Instagram_ como ejemplo e imagine que en lugar de una cuenta centralizada protegida con contraseña, tiene su " identidad criptográfica " vinculada al recurso web / móvil.

Eso es lo que le ofrece Wallet Software. La clave privada de esta identidad (un secreto, y usted puede actuar en nombre de esta identidad) se almacena en su dispositivo local y nunca se conecta, por lo que nadie más que usted puede controlar esta identidad. Con esta identidad, se pueden realizar diferentes acciones, tanto centralizada (recurso web controlado por una autoridad central) y descentralizado (que es una red diferente de la www tradicional, cuyo objetivo es eliminar la autoridad central) redes , utilizando la página web como punto de acceso y / o como interfaz gráfica de usuario. El objetivo de esta "identidad criptográfica" es que sus acciones están protegidas criptográficamente y nadie puede cambiar lo que ha firmado ni su firma.

Hoy en día, las capacidades computacionales y de almacenamiento de las redes descentralizadas tolerantes a fallas como Ethereum, EOS o Tron son limitadas. Si fueran escalables, podríamos usar redes descentralizadas para almacenar toda la aplicación descentralizada, incluida su interfaz gráfica de usuario, datos y lógica comercial. En este caso, llamaríamos a estas aplicaciones verdaderamente descentralizadas / distribuidas.

Sin embargo, debido a que esas redes no son escalables en la actualidad, combinamos diferentes enfoques para lograr el máximo nivel de descentralización para nuestras aplicaciones. El back-end "tradicional" como lo conocemos no va a ninguna parte. Por ejemplo:

  • Usamos back-end para alojar front-end para una aplicación descentralizada.
  • Usamos back-end para integraciones con cualquier otra tecnología y servicio existente. Las aplicaciones reales de clase mundial no pueden vivir en un entorno aislado.
  • Usamos el back-end para almacenar y procesar cualquier cosa lo suficientemente grande para una red descentralizada (blockchain en particular). Prácticamente, toda la aplicación y su lógica de negocios se almacenan en algún lugar del mundo, excluyendo solo la parte blockchain. Sin mencionar que IPFS y capas de almacenamiento similares no pueden garantizar la accesibilidad de los archivos, por lo que tampoco podemos confiar en ellos sin alojar los archivos nosotros mismos. En otras palabras, siempre se necesita un servidor en ejecución dedicado.

No hay forma de crear una aplicación segura y parcialmente descentralizada sin utilizar un back-end sólido a partir de hoy, y el objetivo de este artículo es explicar cómo hacerlo correctamente.

(Des) centralización y tokens

Da la casualidad de que casi todas las aplicaciones descentralizadas de hoy se basan en los llamados tokens: criptomonedas personalizadas (o simplemente clonadas) que impulsan una aplicación descentralizada en particular. Los tokens son simplemente una moneda o activos programables, eso es todo.

Por lo general, un token es un "contrato inteligente" (un programa personalizado) escrito en la parte superior de la plataforma descentralizada como Ethereum. Al poseer algunos tokens, básicamente puede obtener diferentes servicios en un recurso web o aplicación móvil, y cambiar este token por otra cosa. El punto clave aquí es que el token vive por sí solo y no está controlado por una autoridad central.

Hay muchos ejemplos de aplicaciones que se construyen alrededor de tokens: desde numerosos juegos coleccionables como CryptoKitties (tokens ERC721) hasta aplicaciones más orientadas a servicios como LOOM Network, o incluso navegadores como Brave y plataformas de juegos como DreamTeam (tokens compatibles con ERC20).

Los propios desarrolladores determinan y deciden cuánto control tendrán (o no) sobre sus aplicaciones. Pueden construir la lógica empresarial de toda la aplicación sobre contratos inteligentes (como lo hizo CryptoKitties), o no pueden hacer ningún uso de los contratos inteligentes, centralizando todo en sus servidores. Sin embargo, el mejor enfoque está en algún punto intermedio.

Back-end para redes descentralizadas

Desde un punto de vista técnico, tiene que haber un puente que conecte tokens y otros contratos inteligentes con la aplicación web / móvil.

En las aplicaciones totalmente descentralizadas de hoy en día, donde los clientes interactúan directamente con los contratos inteligentes, este puente se reduce a capacidades API JSON RPC de API públicas o grupos de nodos como Infura, que a su vez se ven obligados a existir debido al hecho de que no todos los dispositivos pueden ejecutar y dar soporte a su nodo de red individual. Sin embargo, esta API proporciona un conjunto de funciones básicas y muy estrechas, que apenas permiten realizar consultas simples ni agregar datos de manera eficiente. Debido a esto, eventualmente, el back-end personalizado entra en acción, haciendo que la aplicación esté semi-centralizada.

Toda la interacción con la red descentralizada se puede reducir a solo uno o dos puntos, según las necesidades de la aplicación:

  1. Escuchar los eventos de la red (como transferencias de tokens)/ leyendo el estado de la red .
  2. Publicar transacciones (invocando funciones de contratos inteligentes que cambian el estado, como la transferencia de tokens).

Implementar ambos puntos es bastante complicado, especialmente si queremos construir una solución de back-end segura y confiable. Estos son los puntos principales que vamos a desglosar a continuación:

  • En primer lugar, en Ethereum, la recuperación de eventos no está lista para producción desde el primer momento. Por múltiples razones: los nodos de la red pueden fallar mientras obtienen una gran cantidad de eventos, los eventos pueden desaparecer o cambiar debido a bifurcaciones de la red, etc. Tenemos que construir una capa de abstracción para sincronizar eventos de la red y garantizar su entrega confiable.
  • Lo mismo para la publicación de transacciones, tenemos que abstraer las cosas de bajo nivel de Ethereum como los contadores nonce y las estimaciones de gas, así como la reedición de transacciones, proporcionando una interfaz confiable y estable. Además, la publicación de transacciones implica el uso de claves privadas, lo que requiere una seguridad de back-end avanzada.
  • Seguridad. Nos lo tomaremos en serio y afrontaremos que es imposible garantizar que las claves privadas nunca se verán comprometidas en un back-end. Afortunadamente, existe un enfoque para diseñar una aplicación descentralizada sin siquiera la necesidad de que las cuentas de back-end sean altamente seguras.

En nuestra práctica, todo esto nos hizo crear una solución de back-end robusta para Ethereum que llamamos Ethereum Gateway . Extrae otros microservicios de la diversión de Ethereum y proporciona una API confiable para trabajar con él.

Como una startup de rápido crecimiento, no podemos divulgar la solución completa (todavía) por razones obvias, pero voy a compartir todo lo que necesita saber para que su aplicación descentralizada funcione sin problemas. Sin embargo, si tiene alguna pregunta o consulta específica, no dude en ponerse en contacto conmigo. ¡Los comentarios a este artículo también son muy apreciados!

Arquitectura de aplicaciones descentralizadas

Esta parte del artículo depende en gran medida de las necesidades de una aplicación descentralizada en particular, pero intentaremos resolver algunos patrones de interacción básicos sobre los cuales se construyen estas aplicaciones (ÐPlatform = Plataforma descentralizada = Ethereum / EOS / Tron / Whatever):

Cliente ⬌ ÐPlataforma : aplicaciones totalmente descentralizadas .

El cliente (navegador o aplicación móvil) se comunica con la plataforma descentralizada directamente con la ayuda del software de “billetera” Ethereum como Metamask, Trust o billeteras de hardware como Trezor o Ledger. Ejemplos de DApps construidas de esta manera son CryptoKitties, Loom's Delegated Call, las propias carteras de cifrado (Metamask, Trust, Tron Wallet, otras), intercambios de cifrado descentralizados como Etherdelta, etc.

ÐPlataformaClienteBack EndÐPlataforma : aplicaciones centralizadas o semi-centralizadas .

La interacción del cliente con la plataforma descentralizada y el servidor tiene poco en común. El buen ejemplo de esto es cualquier intercambio de cifrado ( centralizado ) actual, como BitFinex o Poloniex: las monedas que negocia en los intercambios simplemente se registran en la base de datos tradicional. Puede "recargar" el saldo de su base de datos enviando activos a una dirección específica (ÐPlataforma ⬌ Cliente) y luego retirar activos después de algunas acciones en la aplicación (Back End ⬌ ÐPlataforma), sin embargo, todo lo que hace en términos de una "ÐApp" en sí mismo (Cliente ⬌ Back End) no implica su interacción directa con la ÐPlataforma.

Otro ejemplo es Etherscan.io, que utiliza un enfoque semi-centralizado : puede realizar todas las acciones descentralizadas útiles allí, pero la aplicación en sí no tiene sentido sin su back-end integral (Etherscan sincroniza continuamente las transacciones, analiza los datos y los almacena, proporcionando en última instancia una API / UI completa).

Algo intermedio: aplicaciones fijas, centralizadas o semi-centralizadas .

Los enfoques anteriores combinados. Por ejemplo, podemos tener una aplicación que brinde varios servicios a cambio de cripto, permitiéndole iniciar sesión y firmar información con su identidad criptográfica.

Con suerte, el patrón de interacción de las aplicaciones totalmente descentralizadas (Cliente ⬌ Ð Plataforma) no plantea ninguna pregunta. Al confiar en servicios tan sorprendentes como Infura o Trongrid, uno puede simplemente crear una aplicación que no requiera un servidor en absoluto. Casi todas las bibliotecas del lado del cliente como Ethers.js para Ethereum o Tron-Web para Tron pueden conectarse a estos servicios públicos y comunicarse con la red. Sin embargo, para consultas y tareas más complejas, es posible que deba asignar su propio servidor de todos modos.

El resto de patrones de interacción que involucran back-end hacen que las cosas sean más interesantes y complejas. Para poner todo esto en una imagen, imaginemos un caso en el que nuestro back-end reacciona a algún evento en la red. Por ejemplo, el usuario publica una transacción de asignación que nos da permiso para cobrarle. Para realizar un cargo, tenemos que publicar la transacción de cargo en respuesta al evento de asignación emitido:

Desde el punto de vista del back-end, esto es lo que sucede:

  1. Escuchamos un evento de red en particular al sondear continuamente la red.
  2. Una vez que obtenemos un evento, realizamos alguna lógica comercial y luego decidimos publicar una transacción en respuesta.
  3. Antes de publicar la transacción, queremos asegurarnos de que probablemente se extraerá (en Ethereum, la estimación exitosa del gas de la transacción significa que no hay errores en el estado actual de la red ). Sin embargo, no podemos garantizar que la transacción se extraiga correctamente .
  4. Usando una clave privada, firmamos y publicamos la transacción. En Ethereum también tenemos que determinar el precio del gas y el límite de gas de la transacción.
  5. Después de publicar la transacción, sondeamos continuamente la red para conocer su estado.
  6. Si tarda demasiado y no podemos obtener el estado de la transacción, tenemos que volver a publicarla o activar un "escenario de error". Las transacciones se pueden perder por varias razones: congestión de la red, caída de pares, aumento de la carga de la red, etc. En Ethereum, también puede considerar volver a firmar una transacción con un precio de gas diferente (real).
  7. Una vez que finalmente obtenemos nuestra transacción, podemos realizar más lógica comercial si es necesario. Por ejemplo, podemos notificar a otros servicios de back-end sobre el hecho de que se completa la transacción. Además, considere esperar un par de confirmaciones antes de tomar decisiones finales con respecto a la transacción: la red está distribuida y, por lo tanto, el resultado puede cambiar en cuestión de segundos.

Como puede ver, ¡están sucediendo muchas cosas! Sin embargo, es posible que su aplicación no requiera algunos de estos pasos, dependiendo de lo que esté tratando de lograr. Pero, construir un back-end robusto y estable requiere tener una solución para todos los problemas mencionados anteriormente. Analicemos esto.

Aplicaciones descentralizadas back-end

Aquí quiero destacar algunos de los puntos en los que surgen la mayoría de las preguntas, a saber:

  • Escuchar eventos de la red y leer datos de la red
  • Publicar transacciones y cómo hacerlo de forma segura

Escuchar eventos de la red

En Ethereum, así como en otras redes descentralizadas, un concepto de eventos de contrato inteligente (o registros de eventos, o simplemente registros) permite que las aplicaciones fuera de la cadena estén al tanto de lo que está sucediendo en la cadena de bloques. Estos eventos pueden ser creados por desarrolladores de contratos inteligentes en cualquier punto del código del contrato inteligente.

Por ejemplo, dentro del conocido estándar de token ERC20, cada transferencia de token tiene que registrar el evento de transferencia, lo que permite que las aplicaciones fuera de la cadena sepan que se ha producido una transferencia de token. Al "escuchar" estos eventos podemos realizar cualquier (re) acción. Por ejemplo, algunas criptomonedas móviles le envían una notificación push / correo electrónico cuando los tokens se transfieren a su dirección.

De hecho, no existe una solución confiable para escuchar eventos de red de forma inmediata. Las diferentes bibliotecas le permiten rastrear / escuchar eventos, sin embargo, hay muchos casos en los que algo puede salir mal, lo que resulta en eventos perdidos o sin procesar. Para evitar perder eventos, tenemos que construir un back-end personalizado, que mantendrá el proceso de sincronización de eventos.

Dependiendo de sus necesidades, la implementación puede variar. Pero para ponerlo en una imagen, aquí está una de las opciones de cómo puede construir una entrega confiable de eventos Ethereum en términos de arquitectura de microservicio:

Estos componentes funcionan de la siguiente manera:

  1. El servicio de back-end de sincronización de eventos sondea constantemente la red, tratando de recuperar nuevos eventos. Una vez que hay algunos eventos nuevos disponibles, envía estos eventos al bus de mensajes. Tras el envío exitoso del evento al bus de mensajes, como en blockchain, podemos guardar el bloque del último evento para solicitar nuevos eventos de este bloque la próxima vez. Tenga en cuenta que recuperar demasiados eventos a la vez puede resultar en solicitudes siempre fallidas, por lo que debe limitar la cantidad de eventos / bloques que solicita de la red.
  2. El bus de mensajes (por ejemplo, Rabbit MQ) enruta el evento a cada cola que se configuró individualmente para cada servicio de back-end. Antes de la publicación de eventos, el servicio de back-end de sincronización de eventos especifica la clave de enrutamiento (por ejemplo, una dirección de contrato inteligente + tema de evento), mientras que los consumidores (otros servicios de back-end) crean colas que se suscriben solo para eventos particulares.

Como resultado, cada servicio de back-end obtiene solo los eventos que necesita. Además, el bus de mensajes garantiza la entrega de todos los eventos una vez que se publican en él.

Por supuesto, puede usar otra cosa en lugar del bus de mensajes: devoluciones de llamada HTTP, sockets, etc. En este caso, deberá averiguar cómo garantizar la entrega de devoluciones de llamada usted mismo: administre reintentos de devolución de llamada exponenciales / personalizados, implemente monitoreo personalizado y pronto.

Publicar transacciones

Hay un par de pasos que debemos realizar para publicar una transacción en la red descentralizada:

  1. Preparando la transacción. Junto con los datos de la transacción, este paso implica solicitar el estado de la red para saber si esta transacción es válida y se va a extraer (estimación de gas en Ethereum) y el número secuencial de la transacción (nonce en Ethereum). Algunas de las bibliotecas intentan hacer esto bajo el capó, sin embargo, estos pasos son importantes.
  2. Firma de la transacción. Este paso implica el uso de la clave privada. Lo más probable es que aquí desee incrustar la solución de ensamblaje de clave privada personalizada (por ejemplo).
  3. Publicar y volver a publicar la transacción. Uno de los puntos clave aquí es que su transacción publicada siempre tiene la posibilidad de perderse o eliminarse de la red descentralizada. Por ejemplo, en Ethereum, la transacción publicada se puede cancelar si el precio del gas de la red aumenta repentinamente. En este caso, debe volver a publicar la transacción. Además, es posible que desee volver a publicar la transacción con otros parámetros (al menos con un precio de gas más alto) para que se extraiga lo antes posible. Por lo tanto, volver a publicar la transacción puede implicar volver a firmarla, si la transacción de reemplazo no se firmó previamente (con diferentes parámetros).

Al utilizar los enfoques anteriores, puede terminar construyendo algo similar a lo que se presenta en el diagrama de secuencia a continuación. En este diagrama de secuencia en particular, demuestro (¡en general!) Cómo funciona la facturación recurrente de blockchain (hay más en un artículo vinculado):

  1. El usuario ejecuta una función en un contrato inteligente, que en última instancia permite que el back-end realice una transacción de cargo exitosa.
  2. Un servicio de back-end responsable de una tarea en particular escucha el evento de asignación de cargo y publica una transacción de cargo.
  3. Una vez que se extrae la transacción de carga, este servicio de back-end responsable de una tarea en particular recibe un evento de la red Ethereum y realiza alguna lógica (incluida la configuración de la próxima fecha de carga).

Seguridad de back-end y contratos inteligentes

La publicación de transacciones siempre implica el uso de una clave privada . Quizás se pregunte si es posible mantener seguras las claves privadas. Bueno, sí y no. Existen numerosas estrategias complejas y diferentes tipos de software que permiten almacenar claves privadas en el back-end de forma bastante segura. Algunas soluciones de almacenamiento de claves privadas utilizan bases de datos distribuidas geográficamente, mientras que otras incluso sugieren el uso de hardware especial. Sin embargo, en cualquier caso, el punto más vulnerable de una aplicación semi-centralizada es donde la clave privada se ensambla y se usa para firmar una transacción (o, en caso de hardware especial, un punto para activar un procedimiento de firma de transacción). Por lo tanto, en teoría, no existe una solución 100% confiable que permita una protección a prueba de balas para no comprometer las claves privadas almacenadas.

Ahora piensa de esta manera. En muchos casos, ni siquiera necesita proteger las claves privadas en el back-end con tanta frecuencia. En cambio, puede diseñar contratos inteligentes y toda la aplicación de tal manera que una fuga de clave privada no afecte su comportamiento habitual . Con este enfoque, no importa cómo interactúan las cuentas autorizadas con el contrato inteligente. Simplemente están "activando" un contrato inteligente para que haga su trabajo habitual, y el contrato inteligente en sí mismo realiza cualquier validación requerida. Yo lo llamo el "patrón de cuentas operativas".

De esta forma, en caso de emergencia:

  • Lo único que el atacante puede robar es una pequeña cantidad de Ether (a partir de Ethereum) depositada en las cuentas operativas para la publicación de transacciones.
  • El atacante no podrá dañar la lógica del contrato inteligente ni a nadie que esté involucrado en el proceso.
  • Las cuentas operativas comprometidas se pueden reemplazar rápidamente por otras, sin embargo, esto requiere el reemplazo manual (generar nuevas cuentas y reautorizar cuentas en todos los contratos inteligentes) o desarrollar una solución adicional que hará toda la magia con una sola transacción de un super -Cuenta maestra segura (hardware o multi-sig).

Por ejemplo, en nuestra solución de facturación recurrente para Ethereum, no importa lo que suceda en un back-end, el contrato inteligente de facturación recurrente está diseñado de tal manera que tenemos un mes completo para reemplazar las cuentas comprometidas si alguna de ellas está comprometida. .

Pero aún así, si desea que el almacenamiento de su clave privada de back-end sea lo más seguro posible, puede intentar usar Vault con un excelente complemento para Ethereum que almacena y administra cuentas de Ethereum (también, esté atento a nuestros módulos de código abierto: nosotros están a punto de lanzar algo similar pronto). No voy a profundizar en los detalles aquí, aunque puede visitar los proyectos vinculados y aprender de ellos usted mismo.

Esto ni siquiera es todo lo que tengo que decir. Sin embargo, este artículo resultó ser mucho más largo de lo que esperaba, así que tengo que parar. Suscríbase a mi medio / otras redes si está interesado en software, criptografía, consejos de viaje o simplemente quiere seguir algo interesante. Espero haberle proporcionado una gran cantidad de información valiosa y la encontrará útil. ¡No dudes en comentar y difundir este artículo!