Arquitectura de aplicaciones descentralizadas: patrones de back-end, seguridad y 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 principales sobre cómo diseñar e implementar adecuadamente los backend y los contratos inteligentes para aplicaciones descentralizadas, tomando Ethereum como 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 problemas de seguridad
  • Cómo diseñar adecuadamente los 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 de que blockchain se enfrenta a muchas dificultades de adopción y regulación en la actualidad, es un tipo de tecnología que está aquí para quedarse, ya sea blockchain, hashgraph, tempo o cualquier otra tecnología de contabilidad distribuida por venir, independientemente del algoritmo.

El valor principal 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 pueden modificarse después de la creación ni manipularse durante la ejecución. En otras palabras, estos programas siempre se ejecutan según lo diseñado, y ninguna de las partes puede influir en su comportamiento.

Esta definición es válida para muchas criptomonedas que existen hoy en día si las consideramos como programas que definen cómo se pueden transferir 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 / ..., en contraste con 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 tecnologías front-end y back-end tradicionales son lo que llamamos aplicaciones descentralizadas (ÐApps) hoy. 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.

Si alguien me pidiera que dibujara cómo funcionan las DApps hoy, probablemente habría dibujado esto

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

Eso es lo que le proporciona Wallet Software. La clave privada de esta identidad (un secreto, que puede actuar en nombre de esta identidad) se almacena en su dispositivo local y nunca se conecta, por lo que nadie puede controlar esta identidad excepto usted. Con esta identidad, puede realizar diferentes acciones tanto en redes centralizadas (recursos web controlados por una autoridad central) como descentralizadas (que es una red diferente de las redes tradicionales www, cuyo objetivo es eliminar la autoridad central), utilizando el sitio 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 empresarial. En este caso, llamaríamos a estas aplicaciones verdaderamente descentralizadas / distribuidas.

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

  • Usamos back end para alojar front end para una aplicación descentralizada.
  • Utilizamos back-end para integraciones con otras tecnologías y servicios existentes. Las aplicaciones reales de clase mundial no pueden vivir en un entorno aislado.
  • Usamos back-end para almacenar y procesar cualquier cosa lo suficientemente grande como 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 tanto, tampoco podemos confiar en ellos sin alojar los archivos nosotros mismos. En otras palabras, siempre existe la necesidad de un servidor dedicado dedicado.

No hay forma de construir 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.

(De) centralización y tokens

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

Mientras que los contratos inteligentes de token determinan cómo los usuarios pueden transferir tokens, los contratos inteligentes de aplicaciones pueden extender todo lo que falta en los contratos inteligentes de token. Ambos contratos inteligentes se ejecutan sobre redes descentralizadas

Por lo general, un token es un "contrato inteligente" (un programa personalizado) escrito en la parte superior de la plataforma descentralizada como Ethereum. Al ser propietario de 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 solo y no está controlado por una autoridad central.

Hay muchos ejemplos de aplicaciones que se crean en torno a 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 toda la lógica de negocios de la aplicación sobre contratos inteligentes (como lo hizo CryptoKitties), o no pueden hacer uso de 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 contratos inteligentes, este puente se reduce a las capacidades de 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 soportar 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 se inicia, haciendo que la aplicación sea 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) / leer el estado de la red.
  2. Publicación de transacciones (invocando funciones de contrato inteligente que cambian de estado, como la transferencia de tokens).

Implementar ambos puntos es bastante complicado, especialmente si queremos construir una solución de fondo 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. Debido a múltiples razones: los nodos de la red pueden fallar al recuperar una gran cantidad de eventos, los eventos pueden desaparecer o cambiar debido a las bifurcaciones de la red, etc. Tenemos que construir una capa de abstracción para sincronizar los eventos de la red y garantizar su entrega confiable.
  • Lo mismo para la publicación de transacciones, tenemos que resumir las cosas de bajo nivel de Ethereum como los contadores de nonce y las estimaciones de gas, así como la republicació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 seguridad avanzada de back-end.
  • Seguridad. Vamos a tomarlo en serio y nos daremos cuenta de que es imposible garantizar que las claves privadas nunca se vean 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 estén altamente protegidas.

En nuestra práctica, todo esto nos hizo crear una solución de fondo sólida para Ethereum que llamamos Ethereum Gateway. Resume 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 revelar 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!

Monitoreo de back-end para Ethereum. El monitor muestra actividades principalmente relacionadas con nuestra función de facturación recurrente (aunque puede ver picos cada hora).

Arquitectura de aplicaciones descentralizadas

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

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 Ethereum "wallet" como Metamask, Trust o billeteras de hardware como Trezor o Ledger. Ejemplos de compilación de DApps de esta manera son CryptoKitties, la Llamada delegada de Loom, las propias billeteras criptográficas (Metamask, Trust, Tron Wallet, otras), las criptobolsas descentralizadas como Etherdelta, etc.

Ð Plataforma ⬌ Cliente ⬌ Back 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 criptográfico (centralizado) hoy en día, como BitFinex o Poloniex: las monedas que intercambia 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í (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í misma no tiene sentido sin su back-end integral (Etherscan sincroniza continuamente transacciones, analiza datos y los almacena, en última instancia, proporciona una API / IU integral).

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

Los enfoques anteriores combinados. Por ejemplo, podemos tener una aplicación que proporcione varios servicios a cambio de criptografía, lo que le permite 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 simplemente puede 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 for 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 los 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 cobrarles. Para realizar un cargo, tenemos que publicar la transacción de cargo en respuesta al evento de asignación emitido:

Un ejemplo de flujo de la reacción del servidor a la acción del usuario en la red descentralizada

Desde el punto de vista final, esto es lo que sucede:

  1. Escuchamos un evento de red en particular sondeando continuamente la red.
  2. Una vez que obtenemos un evento, realizamos cierta 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 extraiga (en Ethereum, la estimación exitosa del gas de 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 con éxito.
  4. Con 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, encuestamos continuamente a la red por su estado.
  6. Si tarda demasiado y no podemos obtener el estado de la transacción, tenemos que volver a publicarla o desencadenar un "escenario de error". Las transacciones pueden perderse 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. Después de que finalmente extraemos nuestra transacción, podemos realizar más lógica de negocios si es necesario. Por ejemplo, podemos notificar a otros servicios de back-end sobre el hecho de que la transacción se ha completado. Además, considere esperar un par de confirmaciones antes de tomar decisiones finales con respecto a la transacción: la red se distribuye y, por lo tanto, el resultado puede cambiar en cuestión de segundos.

Como puede ver, ¡están sucediendo muchas cosas! Sin embargo, su aplicación puede no requerir 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 que surgen de la mayoría de las preguntas, a saber:

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

Escuchar eventos de red

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

Por ejemplo, dentro del conocido estándar de token ERC20, cada transferencia de token debe registrar el evento Transfer, 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 billeteras criptográficas 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 que algo puede salir mal, lo que resulta en eventos perdidos o no procesados. Para evitar perder eventos, tenemos que crear un back-end personalizado, que mantendrá el proceso de sincronización de eventos.

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

Entrega confiable de eventos Ethereum a todos los servicios de back-end

Estos componentes funcionan de la siguiente manera:

  1. El servicio back-end de sincronización de eventos sondea constantemente la red e intenta 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, en cuanto a 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 que siempre fallan, 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 fondo. 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 están suscritas solo para eventos particulares.

Como resultado, cada servicio de fondo solo obtiene 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 algo más en lugar del bus de mensajes: devoluciones de llamada HTTP, sockets, etc. En este caso, tendrá que descubrir 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.

Publicación de 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 averiguar si esta transacción es válida y se 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. Firmando 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. Publicación y republicación de la transacción. Uno de los puntos clave aquí es que su transacción publicada siempre tiene la oportunidad de perderse o caerse de la red descentralizada. Por ejemplo, en Ethereum, la transacción publicada se puede eliminar 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 extraerla 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).
Los puntos anteriores con respecto a la publicación de transacciones Ethereum visualizados

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 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 finalmente permite que el back-end realice una transacción de carga exitosa.
  2. Un servicio de back-end responsable de una tarea 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 particular recibe un evento de la red Ethereum y realiza cierta lógica (incluida la configuración de la próxima fecha de carga).
El diagrama de secuencia general de cómo funciona la facturación recurrente de blockchain, que demuestra la interacción entre los servicios de back-end y la red Ethereum

Back End Security y contratos inteligentes

La publicación de transacciones siempre implica el uso de una clave privada. Tal vez se pregunte si es posible mantener seguras las claves privadas. Pues 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 se ensambla la clave privada y se utiliza para firmar una transacción (o, en el caso de hardware especial, un punto de activación de un procedimiento de firma de transacción). Por lo tanto, teóricamente, no existe una solución 100% confiable que permita la protección a prueba de balas de 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úen las cuentas autorizadas con el contrato inteligente. Simplemente están "activando" un contrato inteligente para hacer su trabajo habitual, y el contrato inteligente en sí realiza cualquier validación requerida. Lo llamo el "patrón de cuentas operativas".

Patrón de cuentas operativas para aplicaciones descentralizadas, donde no necesita seguridad de grado militar para sus cuentas de back-end

De esta manera, en caso de emergencia:

  • Lo único que el atacante puede robar es una pequeña cantidad de Ether (a partir de Ethereum) depositado 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 (generación de cuentas nuevas y reautorización de cuentas en todos los contratos inteligentes) o el desarrollo de una solución adicional que hará toda la magia con una sola transacción de una súper cuenta maestra segura (hardware o multi-firma).

Por ejemplo, en nuestra solución de facturación recurrente para Ethereum, sin importar lo que ocurra en un back-end, el contrato inteligente de facturación recurrente está diseñado de tal manera que tengamos un mes entero para reemplazar las cuentas comprometidas si alguna de ellas se ve comprometida .

Pero aún así, si desea que su almacenamiento de claves privadas 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, vigile 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 allí 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 haber proporcionado una gran información valiosa y que le resulte útil. ¡Siéntase libre de comentar y difundir este artículo!