
¡Hola, queridos usuarios de Habr! Este artículo trata sobre la Web 3.0, Internet descentralizada. La Web 3.0 introduce el concepto de descentralización como la base de la Internet moderna. Muchos sistemas y redes de computadoras requieren características de seguridad y descentralización para satisfacer sus necesidades. Un registro distribuido que utiliza la tecnología blockchain proporciona soluciones eficientes para la descentralización.
Blockchain es un registro distribuido. Puede pensar en ella como una gran base de datos que vive para siempre, que nunca cambia a lo largo del tiempo. La cadena de bloques proporciona la base para servicios y aplicaciones web descentralizadas.
Sin embargo, la cadena de bloques es más que una simple base de datos. Sirve para aumentar la seguridad y la confianza entre los miembros de la red, mejorando las transacciones comerciales en línea.
El consenso bizantino aumenta la fiabilidad de la red y resuelve el problema de la coherencia.
La escalabilidad proporcionada por DLT cambia las redes comerciales existentes.
Blockchain ofrece nuevos beneficios muy importantes:
- Previene errores costosos.
- Garantiza transacciones transparentes.
- Digitaliza bienes reales.
- Aplica contratos inteligentes.
- Aumenta la velocidad y la seguridad de los pagos.
Hemos desarrollado un PoE especial para investigar protocolos criptográficos y mejorar las soluciones existentes de DLT y blockchain.
La mayoría de los sistemas de registro público carecen de la propiedad de escalabilidad, lo que hace que su rendimiento sea bastante bajo. Por ejemplo, Ethereum procesa solo ~ 20 tx / s.
Se desarrollaron muchas soluciones para aumentar la escalabilidad mientras se mantiene la descentralización. Sin embargo, solo 2 de cada 3 ventajas (escalabilidad, seguridad y descentralización) se pueden lograr simultáneamente.
El uso de cadenas laterales proporciona una de las soluciones más efectivas.
El concepto de plasma
El concepto Plasma se reduce a la idea de que una cadena raíz procesa un pequeño número de confirmaciones de cadenas secundarias, actuando así como la capa más segura y final para almacenar todos los estados intermedios. Cada cadena secundaria funciona como su propia cadena de bloques con su propio algoritmo de consenso, pero hay algunas advertencias importantes.
- Los contratos inteligentes se crean en una cadena raíz y actúan como puntos de control para las cadenas secundarias dentro de la cadena raíz.
- Se crea una cadena secundaria y funciona como su propia cadena de bloques con su propio consenso. Todos los estados de la cadena infantil están protegidos por pruebas de fraude que garantizan que todas las transiciones entre estados sean válidas y apliquen un protocolo de retiro.
- Los contratos inteligentes específicos para DApp o la lógica de aplicación de la cadena secundaria se pueden implementar en la cadena secundaria.
- Los fondos se pueden transferir de la cadena raíz a la cadena secundaria.
Los validadores reciben incentivos económicos para actuar honestamente y enviar compromisos a la cadena raíz: la capa de liquidación de la transacción final.
Como resultado, los usuarios de DApp que trabajan en la cadena secundaria no tienen que interactuar con la cadena raíz. Además, pueden colocar su dinero en la cadena raíz cuando lo deseen, incluso si la cadena secundaria está pirateada. Estas salidas de la cadena infantil permiten a los usuarios almacenar de forma segura sus fondos con pruebas de Merkle, lo que confirma la propiedad de una cierta cantidad de fondos.
Las principales ventajas del plasma están relacionadas con su capacidad para facilitar significativamente los cálculos que sobrecargan la cadena principal. Además, la cadena de bloques Ethereum puede manejar conjuntos de datos más extensos y paralelos. El tiempo eliminado de la cadena raíz también se transfiere a los nodos Ethereum, que tienen menores requisitos de procesamiento y almacenamiento.
Plasma Cash asigna números de serie únicos a los tokens en línea. Las ventajas de este esquema incluyen la no necesidad de confirmaciones, un soporte más simple para todos los tipos de tokens (incluidos los tokens no fungibles) y la mitigación contra las salidas masivas de una cadena secundaria.
El concepto de "salidas masivas" de una cadena infantil es un problema que enfrenta Plasma. En este escenario, los retiros simultáneos coordinados de una cadena secundaria podrían conducir a un poder informático insuficiente para retirar todos los fondos. Como resultado, los usuarios pueden perder fondos.
Opciones para implementar Plasma

Basic Plasma tiene muchas opciones de implementación.
Las principales diferencias se refieren a:
- archivar información sobre almacenamiento de estado y métodos de presentación;
- tipos de fichas (divisible, indivisible);
- seguridad de transacciones;
- tipo de algoritmo de consenso.
Las principales variaciones de Plasma incluyen:
- Plasma basado en UTXO: cada transacción consta de entradas y salidas. Se puede realizar y gastar una transacción. La lista de transacciones no gastadas es el estado de una cadena secundaria en sí.
- Plasma basado en cuentas: esta estructura contiene el reflejo y el saldo de cada cuenta. Se usa en Ethereum, ya que cada cuenta puede ser de dos tipos: una cuenta de usuario y una cuenta de contrato inteligente. La simplicidad es una ventaja importante de Plasma basado en cuentas. Al mismo tiempo, la falta de escalabilidad es una desventaja. Se utiliza una propiedad especial, "nonce", para evitar la ejecución de una transacción dos veces.
Para comprender las estructuras de datos utilizadas en la cadena de bloques de Plasma Cash y cómo funcionan los compromisos, es necesario aclarar el concepto de Merkle Tree.
Merkle Trees y su uso en plasma
Merkle Tree es una estructura de datos extremadamente importante en el mundo blockchain. Nos permite capturar un determinado conjunto de datos y ocultar los datos, sin embargo, demostrar que cierta información estaba en el conjunto. Por ejemplo, si tenemos diez números, podríamos crear una prueba para estos números y luego probar que algún número en particular estaba en este conjunto. Esta prueba tendría un tamaño constante pequeño, lo que hace que sea económico publicar en Ethereum.
Puede usar este principio para un conjunto de transacciones y demostrar que una transacción en particular está en este conjunto. Esto es precisamente lo que hace un operador. Cada bloque consta de un conjunto de transacciones que se convierte en un árbol Merkle. La raíz de este árbol es una prueba que se publica en Ethereum junto con cada bloque de plasma.
Los usuarios deberían poder retirar sus fondos de la cadena Plasma. Para esto, envían una transacción de "salida" a Ethereum.
Plasma Cash utiliza un árbol Merkle especial que elimina la necesidad de validar un bloque completo. Es suficiente para validar solo aquellas ramas que corresponden al token del usuario.
Para transferir un token, es necesario analizar su historial y escanear solo aquellos que un determinado usuario necesita. Al transferir un token, el usuario simplemente envía el historial completo a otro usuario, que luego puede autenticar todo el historial y, lo más importante, hacerlo muy rápidamente.

Estructuras de datos de Plasma Cash para almacenamiento de estado e historial
Es aconsejable utilizar solo árboles Merkle seleccionados, porque es necesario obtener pruebas de inclusión y no inclusión para una transacción en un bloque. Por ejemplo:
- Escaso árbol de merkle
- Árbol patricia
Hemos desarrollado nuestras propias implementaciones de Sparse Merkle Tree y Patricia Tree para nuestro cliente.
Un árbol de Merkle disperso es similar a un árbol de Merkle estándar, excepto que sus datos están indexados y cada punto de datos se coloca en una hoja que corresponde al índice de este punto de datos.
Supongamos que tenemos un árbol Merkle de cuatro hojas. Llenemos este árbol con las letras A y D, para demostración. La letra A es la primera letra del alfabeto, por lo que la colocaremos en la primera hoja. Del mismo modo, colocaremos D en la cuarta hoja.
Entonces, ¿qué pasa en la segunda y tercera hojas? Deben dejarse vacíos. Más precisamente, se usa un valor especial (por ejemplo, cero) en lugar de una letra.
El árbol finalmente se ve así:

La prueba de inclusión funciona de la misma manera que en un árbol Merkle normal. ¿Qué sucede si queremos demostrar que C no es parte de este árbol de Merkle? Elemental! Sabemos que si C es parte de un árbol, estaría en la tercera hoja. Si C no es parte del árbol, entonces la tercera hoja debería ser cero.
Todo lo que se necesita es una prueba estándar de inclusión de Merkle que muestre que la tercera hoja es cero.
¡La mejor característica de un Sparse Merkle Tree es que proporciona repositorios para valores clave dentro del Merkle Tree!
Una parte del código del protocolo PoE construye un árbol de Merkle disperso:
class SparseTree { //... buildTree() { if (Object.keys(this.leaves).length > 0) { this.levels = [] this.levels.unshift(this.leaves) for (let level = 0; level < this.depth; level++) { let currentLevel = this.levels[0] let nextLevel = {} Object.keys(currentLevel).forEach((leafKey) => { let leafHash = currentLevel[leafKey] let isEvenLeaf = this.isEvenLeaf(leafKey) let parentLeafKey = leafKey.slice(0, -1) let neighborLeafKey = parentLeafKey + (isEvenLeaf ? '1' : '0') let neighborLeafHash = currentLevel[neighborLeafKey] if (!neighborLeafHash) { neighborLeafHash = this.defaultHashes[level] } if (!nextLevel[parentLeafKey]) { let parentLeafHash = isEvenLeaf ? ethUtil.sha3(Buffer.concat([leafHash, neighborLeafHash])) : ethUtil.sha3(Buffer.concat([neighborLeafHash, leafHash])) if (level == this.depth - 1) { nextLevel['merkleRoot'] = parentLeafHash } else { nextLevel[parentLeafKey] = parentLeafHash } } }) this.levels.unshift(nextLevel) } } } }
Este código es bastante trivial. Tenemos un repositorio de valores clave con una prueba de inclusión / no inclusión.
En cada iteración, se llena un nivel específico de un árbol final, comenzando con el último. Dependiendo de si la clave de la hoja actual es par o impar, tomamos dos hojas adyacentes y contamos el hash del nivel actual. Si llegamos al final, escribiríamos una sola raíz de merkle, un hash común.
Debe comprender que este árbol está lleno de valores inicialmente vacíos. Si almacenamos una gran cantidad de ID de token, tendríamos un gran tamaño de árbol, ¡y sería largo!
Hay muchos remedios para esta no optimización, pero hemos decidido cambiar este árbol a un árbol Patricia.
Un árbol Patricia es una combinación de árbol Radix y árbol Merkle.
Una clave de datos Radix Tree almacena la ruta a los datos en sí, lo que nos permite crear una estructura de datos optimizada para la memoria.

Aquí hay una implementación desarrollada para nuestro cliente:
buildNode(childNodes, key = '', level = 0) { let node = {key} this.iterations++ if (childNodes.length == 1) { let nodeKey = level == 0 ? childNodes[0].key : childNodes[0].key.slice(level - 1) node.key = nodeKey let nodeHashes = Buffer.concat([Buffer.from(ethUtil.sha3(nodeKey)), childNodes[0].hash]) node.hash = ethUtil.sha3(nodeHashes) return node } let leftChilds = [] let rightChilds = [] childNodes.forEach((node) => { if (node.key[level] == '1') { rightChilds.push(node) } else { leftChilds.push(node) } }) if (leftChilds.length && rightChilds.length) { node.leftChild = this.buildNode(leftChilds, '0', level + 1) node.rightChild = this.buildNode(rightChilds, '1', level + 1) let nodeHashes = Buffer.concat([Buffer.from(ethUtil.sha3(node.key)), node.leftChild.hash, node.rightChild.hash]) node.hash = ethUtil.sha3(nodeHashes) } else if (leftChilds.length && !rightChilds.length) { node = this.buildNode(leftChilds, key + '0', level + 1) } else if (!leftChilds.length && rightChilds.length) { node = this.buildNode(rightChilds, key + '1', level + 1) } else if (!leftChilds.length && !rightChilds.length) { throw new Error('invalid tree') } return node }
Nos movimos recursivamente y construimos los subárboles separados izquierdo y derecho. Se construyó una clave como camino en este árbol.
Esta solución es aún más trivial. Está bien optimizado y funciona más rápido. De hecho, un árbol de Patricia puede optimizarse aún más mediante la introducción de nuevos tipos de nodo: nodo de extensión, nodo de rama, etc., como se hace en el protocolo Ethereum. Pero la implementación actual satisface todos nuestros requisitos: tenemos una estructura de datos rápida y con memoria optimizada.
Al implementar estas estructuras de datos en el proyecto de nuestro cliente, hemos hecho posible el escalado de Plasma Cash. Esto nos permite verificar el historial de un token y la inclusión / no inclusión del token en un árbol, acelerando en gran medida la validación de los bloques y la propia cadena de plasma del niño.
Enlaces:
- Papel blanco plasma
- Git Hub
- Descripción de casos de uso y arquitectura
- Papel de red de rayos