Capítulo 1. Conceptos de Blockchain
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
Fundamentalmente, una cadena de bloques es una estructura de datos. Es una lista enlazada, o cadena, de "bloques" únicos. Cada bloque apunta al anterior, y es en sí mismo una lista de transacciones. Sobre esta estructura de datos de lista de listas relativamente sencilla se asienta la innovación clave que nos han proporcionado las cadenas de bloques: un protocolo sobre cómo se añaden bloques a la cadena sin ninguna autoridad central. La Figura 1-1 ilustra esta estructura.
Las criptomonedas que han aparecido junto con las cadenas de bloques son un medio para conseguir un fin, ya que proporcionan incentivos para que la gente ejecute software que asegure la red. Por primera vez en la historia, tenemos la capacidad de compartir información digital sin confiar en ninguna persona, gobierno, organización o corporación para facilitar la interacción. Ethereum proporciona una plataforma criptográficamente segura para almacenar, actualizar y eliminar datos de una cadena de bloques utilizando lo que se conoce como "contratos inteligentes". Todavía estamos en los primeros días de aprendizaje sobre cómo utilizar los contratos inteligentes para mejorar las cosas en el mundo real, y es difícil predecir cómo se utilizará la tecnología en el futuro. Al igual que ocurrió con la "World Wide Web" en la década de 1990, se ha producido una afluencia global de solucionadores de problemas inspiradores y creativos que trabajan cada día para implementar aplicaciones descentralizadas (DApps) que esperan que "hagan mella en el universo".
Dedicaremos la mayor parte de este libro al lenguaje de programación Solidity y al desarrollo de contratos inteligentes. Solidity es un popular lenguaje de programación para desarrollar contratos inteligentes, y fue diseñado para ejecutarse en la Máquina Virtual de Ethereum (EVM). Solidity no fue el primer lenguaje de programación que se ejecutó en la EVM, y desde luego no será el último. Muchos otros lenguajes, como Vyper, se escribirán para ejecutarse en la EVM, intentando mejorar el diseño de Solidity o proporcionar potentes lenguajes de dominio específico. Antes de adentrarnos en el revolucionario mundo de los contratos inteligentes, necesitamos sentar una base conceptual sobre la que construir. Debido a la naturaleza históricamente única de ejecutar código en una cadena de bloques, es fundamental que los desarrolladores comprendan suficientemente cómo encajan las piezas bajo las potentes abstracciones que proporciona Ethereum.
Breve historia
El concepto de "cadena de bloques" nació del libro blanco de Bitcoin publicado en 2008 por el seudónimo Satoshi Nakamoto. Aunque el término "cadena de bloques" no aparece realmente en el documento, el concepto se articuló de forma concisa. Las transacciones de intercambio de valor entran en una red entre iguales y se agrupan periódicamente en "bloques" o listas. Cuando se conserva un "bloque" de transacciones, se "encadena" al bloque anterior. Esta estructura de datos de sólo apéndice y el protocolo que la construye crean un registro inmutable de las transacciones.
El lanzamiento de Bitcoin a principios de 2009 marcó el inicio de las redes públicas de cadenas de bloques. Desde entonces, innumerables criptomonedas han intentado aprovechar el éxito de Bitcoin como nueva forma de moneda. Muchos de los primeros usuarios de Bitcoin se dieron cuenta de que las propiedades de una cadena de bloques tenían aplicaciones más allá de las transacciones financieras. Surgieron comunidades para intentar ampliar, bifurcar y construir sobre Bitcoin con el fin de extenderlo en direcciones que no se habían concebido previamente. En última instancia, sin embargo, el protocolo Bitcoin está limitado a propósito y mal adaptado para la extensión. El prodigio de la cadena de bloques Vitalik Buterin tomó la ambiciosa decisión de dejar de intentar ampliar Bitcoin y, en su lugar, crear un protocolo más general desde cero. En 2013, Vitalik escribió el libro blanco de Ethereum.
Cuando Ethereum se lanzó en 2015, se convirtió rápidamente en una de las criptomonedas más valiosas del planeta, sólo superada por Bitcoin. Los mercados valoraron Ethereum porque proporcionaba una plataforma para implementar y ejecutar contratos inteligentes en una cadena de bloques pública. El término "contrato inteligente" fue acuñado por Nick Szabo en 1994. La idea entonces era que muchos contratos legales, notariales y otros acuerdos analógicos podrían ejecutarse de forma casi automática utilizando protocolos digitales y firmas criptográficas. A pesar de este contexto histórico, la implementación de los contratos inteligentes en Ethereum se parece más a la programación de propósito general que a algo específico de los contratos legales. La máquina virtual definida por el protocolo de Ethereum es Turing-completa. Esto significa que, siempre que puedas realizar tus cálculos dentro de las limitaciones de un solo bloque, los desarrolladores de contratos inteligentes tienen pocas limitaciones más allá de su propia imaginación.
¿En qué se diferencia una cadena de bloques de las tecnologías con las que hayas trabajado antes? En la siguiente sección, hablaremos de lo que hace única a la cadena de bloques.
El carácter de una Blockchain
Muchos desarrolladores de software habrán trabajado con una pila tecnológica que incluye 1) una interfaz de usuario móvil nativa y/o una interfaz de usuario web 2) con un lenguaje de programación del lado del servidor que, en última instancia, interactúa con 3) una base de datos. En las versiones más básicas de estos sistemas, las interacciones con la base de datos son esencialmente instantáneas y permanentes.
Al igual que una base de datos más típica, las cadenas de bloques pueden almacenar datos arbitrarios, pero comparten pocas similitudes más. Convertirse en un desarrollador de contratos inteligentes competente significa comprender el carácter de una cadena de bloques. No se puede tratar a Solidity simplemente como un lenguaje de programación del lado del servidor; te encontrarás rápidamente perdido y frustrado. A diferencia de las bases de datos típicas, las interacciones incluso con los sistemas de contratos inteligentes más básicos no son instantáneas y no se garantiza que sean permanentes.
A diferencia de una base de datos típica, que es un único programa que se ejecuta en un ordenador, una cadena de bloques suele estar formada por muchos nodos en una red mundial. Cuando nos referimos a "nodos" en el contexto de una cadena de bloques, nos referimos al software que alguien ha instalado en un ordenador y conectado a la red de la cadena de bloques. Al igual que hay muchas implementaciones de software diferentes del protocolo HTTP (Apache, NGINX) llamadas "servidores web", hay muchas implementaciones de software del protocolo Ethereum (geth, Parity) llamadas nodos Ethereum.
A continuación, vamos a discutir cómo podemos utilizar una red para conectar con una cadena de bloques.
Redes descentralizadas
Para trabajar con una base de datos típica, necesitamos una conexión a la base de datos y derechos suficientes para actualizarla. En los casos más sencillos hay que lidiar con una única base de datos, por lo que necesitamos una única dirección IP para establecer la conexión. Todo el sistema depende de que esa base de datos esté disponible. Si la base de datos se bloqueara o perdiera la conectividad, o si se revocaran nuestros derechos, la aplicación dejaría de funcionar. Esto es lo que se denomina un sistema centralizado.
Las cadenas de bloques se diseñaron para funcionar en redes descentralizadas. Las personas y las empresas dirigen los nodos de la red y, en muchos aspectos, todos los nodos son iguales. Esto es posible porque cada nodo contiene el historial completo de todas las transacciones que se han producido en la cadena de bloques. Al ser cada nodo una entidad autosuficiente e independiente, no existe un centro de la red. Todos los nodos validan las transacciones y las propagan a sus pares. Además, algunos nodos también participan en el proceso de creación de bloques, y están incentivados económicamente para hacerlo mediante la recepción de una "recompensa por bloque" en la criptomoneda nativa de la cadena de bloques. A diferencia de nuestro ejemplo de la base de datos, cualquier nodo de una red descentralizada puede unirse o darse de baja a voluntad. No se necesitan permisos o derechos especiales para leer o escribir en la cadena de bloques, siempre que se siga el protocolo.
Sin una base de datos única y centralizada a la que conectarse, ¿cómo encuentran los nodos una conexión a la red? Muchos desarrolladores de contratos inteligentes se rinden ante esta cuestión y utilizan un servicio que oculta esta complejidad tras una API web. Se trata de un compromiso entre acoplar tu sistema a un servicio de terceros y la complejidad de ejecutar tu propio nodo Ethereum. Si decides ejecutar tu propio nodo, tendrá que arrancar por sí mismo y conectarse a la red pública de Ethereum. Cada uno de los principales software de nodos Ethereum tiene una lista codificada de "bootnodes". Se trata de direcciones IP bien conocidas y relativamente fiables que pueden utilizarse para crear un grupo suficientemente grande de conexiones de pares a la red. Si por alguna razón esos nodos no están disponibles o ya no son de confianza, se puede dar al software del nodo una lista personalizada de nodos de arranque para que los utilice.
En 2019, la red pública mundial Ethereum tenía más nodos que ninguna otra red de cadenas de bloques. Con más de 12.000 nodos, es poco probable que dos nodos tengan exactamente el mismo conjunto de pares. Cuando una transacción entra en la red a través de un único peer, se propaga rápidamente por todo el mundo a todos los nodos. Del mismo modo, cuando se añade un nuevo bloque a la cadena de bloques, la noticia se difunde rápidamente. Por desgracia, rápidamente no es lo mismo que instantáneamente, lo que significa que cuando dos bloques son creados al mismo tiempo por dos nodos diferentes, los respectivos pares de esos nodos divergentes estarán operando en dos versiones diferentes de la blockchain. Esto se denomina bifurcación temporal. En general, las bifurcaciones temporales se resuelven concediendo prioridad a la bifurcación que añada el siguiente bloque, lo que se resume en "la cadena más larga gana". Estas reglas vienen determinadas por el protocolo de consenso de la cadena de bloques.
Protocolos de consenso
Sin una fuente central de la verdad, los nodos de una red blockchain necesitan una forma de llegar a un consenso sobre el estado del sistema. Los protocolos de consenso son la forma de conseguirlo, y constituyen un área activa de investigación. Un protocolo de consenso es simplemente un sistema de acuerdo a través de una blockchain. Hay tres protocolos que debemos cubrir, y empezaremos con el Proof-of-Work, que Ethereum utilizó cuando lanzó sus blockchains.
Prueba de trabajo
Cuando oigas hablar de "minar éter" o "criptominería", esta terminología se debe a la naturaleza del protocolo Proof-of-Work (PoW). Al igual que un minero en busca de oro, el protocolo PoW para crear un bloque requiere un esfuerzo considerable, y cuanto más esfuerzo emplees, más probabilidades tendrás de "minar un bloque". También de forma similar a los mineros en busca de oro, la "minería" PoW es competitiva. Cada bloque de Ethereum contiene una recompensa de bloque en la moneda de Ethereum, conocida como "éter". Esta recompensa se concede al minero que consigue añadir un bloque válido a la cadena de bloques antes que cualquier otro minero. El minero lo consigue "hasheando" sucesivamente los datos del bloque en un intento de encontrar un hash criptográfico que tenga unas características específicas. El protocolo determina la rareza de estos "hashes". Esta rareza se denomina "dificultad", el número que determina el esfuerzo relativo que tienen que hacer los mineros para "minar un bloque".
Proof-of-Work utiliza una variable de dificultad para mantener un tiempo de bloque estable a medida que los mineros entran y salen, sumando o restando a la potencia minera total (o hashrate) de la red. Por ejemplo, si la red Ethereum tuviera un hashrate acumulado de 100 tera hashes por segundo, y un gran grupo de mineros se desconectara, reduciendo el hashrate acumulado a 90 TH/s, los bloques tardarían de repente, de media, un 10% más en minarse. El protocolo de Ethereum ajusta la dificultad para garantizar el tiempo de bloque correcto. En este caso, el protocolo se ajustaría haciendo que encontrar bloques fuera un 10% más fácil. Este proceso significa que en caso de cambios significativos en el hashrate de la red, los tiempos de bloque se verán afectados. Además, debido a la naturaleza de fuerza bruta del proceso de minería, la aleatoriedad será una fuente siempre presente de diferencias en los tiempos de bloque, incluso si el hashrate de la red permanece constante.
Prueba de participación
Proof-of-Stake (PoS) siempre ha estado en la hoja de ruta de Ethereum. Si se diseña correctamente, PoS tiene importantes ventajas sobre PoW. En primer lugar, no hay necesidad de quemar grandes cantidades de electricidad para encontrar un bloque válido. Como sugieren sus respectivos nombres, no hay "trabajo" que hacer, sólo "apuesta" que perder. En segundo lugar, la desventaja de ser un mal actor en un sistema PoS puede ser mucho más grave que en PoW. Los creadores de bloques apuestan éter para participar, y si se demuestra que operan de forma maliciosa, esa apuesta puede ser retirada ("recortada"). En PoW, se puede ignorar a los operadores maliciosos, pero no podemos quitarles su hardware. Ese activo vive para corromper otro día. Por último, hay menos economías de escala en PoS. Cuando un minero PoW gana una recompensa por bloque, puede utilizar ese dinero para conseguir ventajas no lineales sobre otros mineros, como una conectividad de red más rápida. En PoS, aunque las personas con más apuestas ganarán más ether, esa ventaja es lineal. Los ricos siguen haciéndose más ricos, pero no de forma exponencial. Dicho esto, en PoW para obtener moneda puedes comprar hardware y conectarte a la red, mientras que en PoS debes comprar a los poseedores de moneda existentes, lo que supone una desventaja para los recién llegados.
Para el desarrollador de contratos inteligentes, el comportamiento de PoS es similar al de PoW. Aún tenemos que determinar cuándo consideramos la finalidad de la transacción, ya que los creadores de bloques (llamados "proponentes" en PoS) aún pueden acabar en diferentes bifurcaciones debido a las particiones de la red. Donde las cosas cambiarán significativamente es cuando Ethereum pase a una cadena de bloques fragmentada .
Bajo el nombre de proyecto "Serenity", Ethereum está introduciendo Proof-of-Stake, sharding y varias otras mejoras como forma de resolver los problemas fundamentales de escalado de Ethereum.1 Serenity también sustituirá el actual EVM por eWASM (Ethereum flavored WebAssembly). Ninguna de estas iniciativas debería tener un efecto significativo en el lenguaje Solidity. La única nueva complicación significativa de será la comunicación entre contratos. Actualmente, cada nodo tiene todos los contratos inteligentes de Ethereum que se han implementado, por lo que es bastante sencillo que los nodos interactúen. Pero cuando tengamos una cadena de bloques fragmentada, los contratos estarán repartidos en más de 1000 cadenas de bloques distintas. El protocolo sobre cómo interactuarán estos contratos aún se está desarrollando, pero se puede afirmar que los desarrolladores de contratos inteligentes ya no podrán asumir que cualquier contrato arbitrario estará disponible para ellos de forma sincrónica. Además, cada dirección de Ethereum tendrá que tener asociado un identificador de fragmento para saber dónde está almacenado su estado.
Prueba de autorización
En algunas situaciones, puede tener sentido utilizar una cadena de bloques restringiendo la creación de bloques sólo a determinadas entidades. Estas situaciones van en contra del caso de uso típico de que esté abierta al público. Este tipo de estatus de creador de bloques restringido lo proporciona el protocolo de Prueba de Autoridad (PoA). PoA se utiliza normalmente para cadenas de bloques privadas que están ocultas dentro de redes internas. Los creadores de bloques simplemente se turnan para añadir el siguiente bloque con la frecuencia correcta. Debido a la naturaleza rotatoria del PoA, las bifurcaciones temporales son mucho menos probables.
Hay muchos más protocolos de consenso, pero están fuera del alcance de este libro. Se trata de un área de investigación activa, por lo que es de esperar que surjan nuevos protocolos de consenso en los próximos años.
Independientemente del protocolo que se utilice, hay una recompensa para cualquier nodo que añada un nuevo bloque a la cadena de bloques. Los creadores de bloques reciben una recompensa por bloque, así como la suma de todas las comisiones por transacción del bloque. En Ethereum, las comisiones por transacción se denominan "gas". Profundizaremos en el tema del gas más adelante en este capítulo.
A efectos de este libro, no necesitamos comprender la mecánica de los protocolos de consenso. Sólo es importante conocer el papel que desempeñan estos protocolos dentro de una cadena de bloques, y entender las diferencias entre los protocolos más populares. Los desarrolladores de contratos inteligentes pueden tratar esencialmente los protocolos de consenso como una caja negra. Tenemos que aprender el comportamiento externo de cada caja, pero los aspectos internos no afectan a nuestro trabajo.
Ahora trabajaremos para comprender cómo se añaden finalmente las transacciones a una cadena de bloques.
Procesamiento de transacciones
En 2019, se añadió un nuevo bloque de transacciones a la cadena de bloques de Ethereum aproximadamente cada 13 segundos, de media. Dicho esto, una muestra de tiempos de bloque en un lapso de 65 horas (entre los bloques 8.662.243 y 8.679.755) contenía un bloque que tardó más de 2 minutos en escribirse en la cadena (bloque 8.674.540) y otros que tardaron sólo 1 segundo. Esta gran variación en los tiempos de los bloques se debe a la naturaleza aleatoria de la Prueba de Trabajo. A medida que Ethereum pase a Proof-of-Stake, esperamos que disminuyan los tiempos de los bloques y su variabilidad.
Aunque los tiempos de los bloques están regulados por el protocolo, el tiempo real que tarda una transacción, desde el momento en que se difunde por primera vez en la red hasta su ejecución en un bloque, puede variar desde unos segundos hasta unas horas. Esta variabilidad se debe a las limitaciones del actual protocolo de Ethereum combinadas con su popularidad. Por ejemplo, si Ethereum puede procesar unas 25 transacciones por segundo, pero hay más de 30 transacciones que entran en la red cada segundo, entonces habrá transacciones que permanecerán sin ejecutar hasta que la demanda de la red se ralentice por debajo de las 25 transacciones por segundo. Estas transacciones pendientes se mantienen en memoria por cada nodo de Ethereum en lo que se llama un "mempool". La forma más fiable de sacar rápidamente una transacción de la mempool e introducirla en un bloque es pagar más tasas al creador del bloque. En Ethereum, estas tarifas se llaman "gas", y como desarrollador de contratos inteligentes, tendrás que considerar con frecuencia los "precios del gas". Mira más sobre el tema del gas y los precios del gas en la Figura 1-2.
Si la misma cuenta tiene dos transacciones en el mempool, los creadores de bloques saben qué transacción elegir primero en función de su "nonce", un contador específico de la cuenta que se incrementa con cada transacción. Esto significa que, aunque la mempool es generalmente un montón de transacciones entre las que los creadores de bloques pueden elegir, en realidad es un montón de colas en los casos en que las cuentas han enviado múltiples transacciones a antes de verlas tener éxito. La Figura 1-3 muestra cómo se secuencian las transacciones de cada cuenta según su nonce.
Independientemente de si una transacción se retrasa por la naturaleza del protocolo o debido a la congestión de la red, como desarrollador, debes tener en cuenta la naturaleza impredecible y asíncrona de las experiencias de tus usuarios con tu sistema de software. En un mundo en el que el software financiero, comercial y de redes sociales se ha ajustado para aceptar transacciones en menos de un segundo, los sistemas respaldados por contratos inteligentes tienen importantes retos de experiencia de usuario. La siguiente sección profundiza un poco más en estos retos.
Finalidad de la transacción
Cuando se ejecuta con éxito una transacción en una base de datos típica, los desarrolladores asumen que sus efectos nunca se revertirán. Sin embargo, en el caso de las cadenas de bloques, es posible que un usuario vea una transacción realizada con éxito incluida en un bloque, sólo para ver que ese bloque queda huérfano y se sustituye por otro diferente. Es posible que este bloque no haya incluido la transacción realizada anteriormente. Esta falta de finalidad en las transacciones de la cadena de bloques se debe a la naturaleza descentralizada de la red. Es posible que dos nodos diferentes creen bloques casi simultáneamente, y que sus respectivos pares estén en diferentes "bifurcaciones" de la cadena de bloques, como ya se ha mencionado. Cualquiera que sea la cadena que cree primero el siguiente bloque, vencerá a la otra bifurcación, y las transacciones realizadas anteriormente con éxito pueden desaparecer en el proceso. Desde el punto de vista del usuario, parece que su transacción, que una vez tuvo éxito, se revierte. Las figuras 1-4 a1-6 ilustran el problema.
Este problema puede resolverse esperando a que se añadan bloques adicionales a la cadena antes de considerar la transacción como finalizada. Cada aplicación tendrá que tener su propio umbral para considerar finalizada una transacción. Las bolsas de criptomonedas más populares esperarán hasta 50 bloques para que una transacción se considere "confirmada" en la red Ethereum. Cuando las apuestas son menores, es menos necesario esperar tanto. Cada aplicación debe considerar cuánto tiempo quiere esperar para considerar definitivas las transacciones. En algunos casos, esta "confirmación" estará integrada en la experiencia del usuario, y en otros, puede ser simplemente una advertencia general de que no todas las transacciones son definitivas.
Ahora que ya hemos hablado de las horquillas temporales, hablemos de sus primas más permanentes .
Horquillas duras
Cada nodo de una red blockchain debe ejecutar un software compatible con el protocolo para participar en la propagación de bloques y transacciones, así como en la creación de nuevos bloques. La comunidad de desarrolladores de nodos blockchain, desarrolladores de protocolos, creadores de bloques, bolsas y desarrolladores de aplicaciones, todos ellos opinan sobre la mejor manera de que el protocolo evolucione para adaptarse a las necesidades de la comunidad. En última instancia, esta evolución se produce a través de los desarrolladores que cambian el software del nodo de la cadena de bloques, y de las personas que ejecutan el software del nodo (lo que llamamos "ejecutores del nodo") que eligen si utilizar la nueva versión o no. A veces, los cambios realizados en el protocolo son tan significativos que la nueva versión no es compatible con las versiones anteriores del software. Esta situación se denomina "bifurcación dura", ya que se creará una nueva cadena de bloques a partir de la anterior, creando una bifurcación en el camino para los ejecutores de nodos, que tendrán que decidir por sí mismos qué bifurcación apoyarán.
Las bifurcaciones duras pueden ser contenciosas o no contenciosas. Las bifurcaciones duras no contenciosas se producen cuando toda la comunidad se actualiza, abandonando de hecho la bifurcación antigua por la nueva. Esto es típico cuando se descubre un defecto grave en el protocolo. Las bifurcaciones duras contenciosas se producen cuando una masa crítica de ejecutores de nodos decide seguir utilizando la bifurcación existente y hacer evolucionar el protocolo antiguo al margen de la nueva versión. Ethereum (ETH) tuvo una bifurcación dura contenciosa en 2016 que hizo que la cadena de bloques anterior sobreviviera y se llamara a sí misma Ethereum Classic (ETC). Podría haber más bifurcaciones duras polémicas a medida que la comunidad implemente los distintos componentes de la versión Serenity.
Nos hemos centrado principalmente en aspectos generales de las cadenas de bloques; ahora, centrémonos en Ethereum específicamente.
Fundamentos de Ethereum
Hemos estado describiendo características relativamente generales de las cadenas de bloques, con algunos ejemplos específicos de Ethereum. Ahora profundizaremos en los fundamentos de Ethereum y discutiremos cómo encajan estas piezas para permitir el desarrollo de contratos inteligentes. Es importante entender los costes de transacción, cómo se identifican las cuentas y los contratos, y cómo se ejecutan las transacciones dentro de los bloques. Estos fundamentos de Ethereum forman la base para comprender cómo diseñar y desarrollar aplicaciones descentralizadas.
Éter y gas
El protocolo Ethereum tiene su propia moneda, llamada éter. El uso fundamental de esta moneda es pagar a los creadores de bloques para que incluyan transacciones en los bloques. Al igual que el dólar estadounidense, el éter es divisible, aunque en fracciones mucho más pequeñas que un céntimo. La unidad más pequeña de éter se llama wei, que es la quintillonésima parte de un éter (para que nos hagamos una idea, un quintillón son mil millones de billones, o1018). Debido al pequeño tamaño de un wei en proporción al éter, a menudo verás el éter denominado por Gwei, sobre todo cuando se trata del precio de la gasolina. Un Gwei son mil millones de wei, y mil millones de Gwei es un éter.
Aunque existe un único protocolo Ethereum, hay más de una red que ejecuta ese protocolo. Es posible crear redes privadas que ejecuten Ethereum, de forma similar a como una "Internet" privada se denomina "intranet". Estas redes privadas siguen operando con éter igual que la red pública de Ethereum, pero su éter no vale nada en el mercado abierto. El éter de la red pública Ethereum se conoce como ETH y tiene valor en el mundo real. Los desarrolladores denominan "mainnet" a la red pública Ethereum. También hay redes públicas de prueba o "testnets" que la comunidad utiliza como entornos de ensayo. Estas redes de prueba suelen tener "faucets", o mecanismos para dar ether gratis a los desarrolladores con el fin de que prueben sus contratos inteligentes. Todas las redes que ejecutan el protocolo Ethereum tienen éter, pero el ETH de mainnet es el que tiene valor real. Para implementar y ejecutar contratos inteligentes en mainnet, necesitamos una cuenta que tenga ETH. Para adquirir ETH tendrás que comprarlo a través de un intercambio, recibirlo de un amigo, ganarlo de un negocio o minarlo tú mismo.
El código de los contratos inteligentes, como Solidity, se compila en bytecode, que proporciona una serie de opcodes a la EVM. Un opcode es una instrucción como PUSH1
o MLOAD
que es interpretada por la EVM. Cada uno de estos opcodes tiene un "coste de gas" asociado.2 Echemos un vistazo a un contrato inteligente sencillo, e inspeccionemos su bytecode y sus opcodes. Afortunadamente, como desarrolladores de contratos inteligentes, no necesitamos comprender el contenido del bytecode ni de los opcodes, ya que podemos entender el Solidity con bastante facilidad. Dicho esto, a medida que progreses como desarrollador de contratos inteligentes, profundizar en estos conceptos de nivel inferior te dará una comprensión más profunda de lo que es posible.
pragma solidity
^
0
.
4
.
25
;
contract
Incrementer
{
uint256
public
count
;
function
addOne
()
public
{
count
++
;
}
}
Compilar este contrato utilizando solc
da como resultado el siguiente bytecode:
6060604052341561000f57600080fd5b60cb8061001d6000396000f300606060405260043610604 9576000357c0100000000000000000000000000000000000000000000000000000000900463ffff ffff168063a7916fac14604e578063febb0f7e146060575b600080fd5b3415605857600080fd5b6 05e6086565b005b3415606a57600080fd5b60706099565b60405180828152602001915050604051 80910390f35b6000808154809291906001019190505550565b600054815600a165627a7a7230582 08e9afbffafd387e67b7c38d8239aaa70fde96a805cebfb6f30517dd68e8664be0029
Y los opcodes según solc
tienen este aspecto:
PUSH1 0x60 PUSH1 0x40 MSTORE CALLVALUE ISZERO PUSH2 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0xCB DUP1 PUSH2 0x1D PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x60 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH1 0x49 JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000 000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0xA7916FAC EQ PUSH1 0x4E JUMPI DUP1 PUSH4 0xFEBB0F7E EQ PUSH1 0x60 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE ISZERO PUSH1 0x58 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x5E PUSH1 0x86 JUMP JUMPDEST STOP JUMPDEST CALLVALUE ISZERO PUSH1 0x6A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x70 PUSH1 0x99 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x0 DUP1 DUP2 SLOAD DUP1 SWAP3 SWAP2 SWAP1 PUSH1 0x1 ADD SWAP2 SWAP1 POP SSTORE POP JUMP JUMPDEST PUSH1 0x0 SLOAD DUP2 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 DUP15 SWAP11 CREATE2 SELFDESTRUCT 0xaf 0xd3 DUP8 0xe6 PUSH28 0x7C38D8239AAA70FDE96A805CEBFB6F30517DD68E8664BE0029000000
El concepto de "gas" existe para desvincular el precio del éter de las comisiones por transacción de Ethereum. Sin esta desvinculación, las comisiones por transacción de Ethereum estarían vinculadas al precio del éter, lo que tendría repercusiones negativas en el ecosistema debido a la volatilidad del valor de ETH. Para desvincular el tipo de cambio entre el gas y el éter, cada transacción de Ethereum establece su propio gasprice
para determinar cuántos wei cuesta una unidad de gas. Cuando los creadores de bloques deciden qué transacciones incluir en un bloque, se ven incentivados a incluir las transacciones que les proporcionarán el gasprice
más generoso para sus cálculos. En un momento dado, existe una tarifa de mercado implícita para el gas. Pagar por debajo de la tasa de mercado significará esperar más que la mayoría de las demás transacciones a que se ejecute tu transacción, mientras que pagar por encima de la tasa de mercado te permitirá salir del mempool antes que la mayoría.
Los creadores de transacciones Ethereum tienen que considerar los costes frente a los beneficios de fijar su precio de gas por encima o por debajo de la tarifa actual del mercado. Algunas transacciones no son sensibles al tiempo y pueden esperar muchas horas para ejecutarse. En este caso, tiene sentido pagar relativamente poco éter por la ejecución de una transacción. Los creadores de bloques finalmente incluyen transacciones de bajo precio de ether cuando el mempool es poco profundo y no hay una masa crítica de transacciones con un generoso gasprice
. A la inversa, algunas transacciones son increíblemente sensibles al tiempo, y son tan valiosas que tiene sentido pagar significativamente por encima del precio de mercado para una rápida inclusión en el bloque. Incluso hay gente que ejecuta programas informáticos para observar el mempool en tiempo real e intentar "adelantarse" a ciertas transacciones financieras, viendo una operación pendiente y adelantándose a ella fijando un gasprice
más alto.
Cada transacción de Ethereum tiene que incluir los atributos gas
y gasprice
, que al multiplicarse establecerán la tarifa máxima de la transacción, denominada en wei. Este atributo gas
establece un límite al número de cálculos que puede realizar la transacción. Si se alcanza ese límite, la ejecución del contrato inteligente se revierte, pero la transacción se sigue escribiendo en la blockchain, y las tasas son consumidas por el creador del bloque. Si la llamada al contrato inteligente finaliza con gas sobrante, éste se devuelve al creador de la transacción. La suma del gas utilizado en todas las transacciones de un bloque no puede superar el valor especificado del bloque gaslimit
. Esto también significa que el uso de gas de una sola transacción no puede superar el del bloque gaslimit
.
La siguiente sección profundizará en los detalles de los actores de una transacción y cómo se identifican.
Cuentas
Las transacciones más básicas de Ethereum son simplemente direcciones de propiedad externa (EOA) que se envían ether entre sí. Además, las transacciones de Ethereum pueden enviarse desde una EOA a un contrato inteligente. Tanto las EOA como los contratos inteligentes se identifican mediante una dirección de Ethereum como la siguiente:
0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5
Una dirección se representa mediante un número hexadecimal. Dada la magnitud de los números que intervienen en la generación de una dirección, es prácticamente imposible generar dos direcciones idénticas. No hay forma de distinguir una dirección de un contrato inteligente de una EOA sin inspeccionar la blockchain. Aunque las cuentas y los contratos tienen direcciones indistinguibles, presentan algunas diferencias importantes.
Cada transacción en la blockchain de Ethereum la inicia un EOA. Los contratos inteligentes no pueden realizar una acción espontáneamente. Pueden llamar a otros contratos inteligentes, pero toda transacción se origina en un EOA. Cuando los contratos son llamados, pueden emitir eventos, almacenar datos, recibir éter, enviar éter a los EOA, o enviar datos o éter a otros contratos. Además de iniciar transacciones, los EOA, por otra parte, sólo pueden recibir éter. No pueden reaccionar a ninguna transacción que les implique del modo en que lo hace un contrato inteligente.
Contratos
Los contratos en Solidity se organizan en un estilo orientado a objetos similar al lenguaje de programación Java. En el lenguaje orientado a objetos, un contrato es en realidad una clase, o una colección de variables de estado y funciones. Para reutilizar la funcionalidad común, los lenguajes orientados a objetos permiten que una clase herede de otra clase (o contratos, en este caso). Como Solidity utiliza function
como palabra clave, denominaremos "funciones" a lo que la mayoría de los lenguajes orientados a objetos denominan "métodos". Las funciones Solidity pueden separarse en dos tipos distintos: de sólo escritura y de sólo lectura.
Las funciones Solidity de sólo lectura se indican con las palabras clave pure
y view
. Estas funciones pueden tomar datos de entrada, leer datos del contrato, operar con esos datos y devolver datos. Las funciones de sólo lectura no pueden cambiar el estado del contrato ni emitir eventos. Dado que no se necesitan actualizaciones en la cadena, las funciones de sólo lectura son instantáneas: son muy similares a las llamadas a la API web, en particular a las peticiones GET. Es importante tener en cuenta que poder omitir las actualizaciones en la cadena significa que las funciones de sólo lectura se pueden llamar sin pagar ningún coste de gas, y no se creará ninguna transacción.
Las funciones de sólo escritura son las predeterminadas en Solidity, por lo que no requieren palabras clave adicionales. A pesar de ser "sólo de escritura", en realidad pueden devolver datos, pero debido a la naturaleza asíncrona de Ethereum, los datos devueltos son prácticamente inútiles, de ahí lo de "sólo de escritura". Estas funciones son los caballos de batalla de Ethereum, y sus datos deben enviarse a través de una transacción e incluirse en un bloque para que la función se ejecute. Un método de sólo escritura fallido revertirá, ya sea porque se ha quedado sin gasolina o ha alcanzado un estado EVM no válido, o debido a una declaración explícita en el contrato, como el fallo de una declaración require
. Un método de sólo escritura exitoso no tiene que cambiar nada en realidad, pero normalmente cambia algo y a menudo emite uno o más eventos en el proceso.
El propósito de los eventos en Ethereum es generalmente doble: proporcionar un registro histórico personalizado de lo que ha ocurrido en el contrato, y permitir a los observadores suscribirse a actualizaciones en tiempo real. Debido a la naturaleza de la cadena de bloques, ya tenemos un registro histórico de todo lo que ha ocurrido, pero los eventos son una forma cómoda de proporcionar registros y actualizaciones más específicos del dominio, de modo que los usuarios no tengan que crear sus propias interpretaciones de las transacciones y transiciones de estado.
Para comprender mejor la mecánica de los contratos inteligentes de Ethereum, centrémonos ahora en los vehículos de su modificación.
Bloques y transacciones
Sólo los creadores de bloques de Ethereum determinan los atributos de un bloque, como qué transacciones se incluyen. Del mismo modo, sólo los usuarios de Ethereum determinan los atributos de una transacción, como a qué contrato enviar datos. Como desarrolladores de contratos inteligentes, a menudo necesitamos conocer el estado del bloque, así como el estado de la transacción que se está ejecutando en ese momento.
Solidity expone en los siguientes atributos de transacción (tx
):
gasprice
-
El precio del gas (en wei) fijado por la EOA que creó la transacción.
origin
-
La dirección de la EOA que creó la transacción. Sorprendentemente, esta dirección rara vez es útil y a menudo es insegura.
Una transacción puede tener un número arbitrario de contratos implicados en su ejecución, siempre que su ejecución se ajuste a las restricciones del bloque gaslimit
. Solidity expone otra serie de atributos relacionados con las transacciones, pero los agrupa en una abstracción de mensajes (msg
). Los mensajes se refieren a la comunicación entre los contratos y cualquier cosa que pueda llamarlos, como otros contratos. Por ejemplo, una llamada a la función de un contrato siempre tendrá un msg.sender
. Ese msg.sender
puede ser igual al tx.origin
o al creador de la transacción, o puede ser la dirección de un contrato intermediario .
Los atributos del mensaje de solidez (msg
) son los siguientes:
data
-
Los bytes brutos de datos que se enviaron a la función externa o pública ejecutada en ese momento. También se denominan datos de llamada.
sender
-
La dirección del llamante de la función externa o pública actualmente ejecutada.
sig
-
Los cuatro primeros bytes de los datos de llamada determinan a qué función se está llamando. También se denomina identificador de la función.
value
-
La cantidad de wei que se envía a esta función.
Solidity expone en los siguientes atributos block
:
number
-
Cada bloque incrementa este número. El bloque génesis era el bloque 0.
timestamp
-
El tiempo en segundos desde la época en que se creó el bloque. También puedes ver código que utiliza su alias,
now
. blockhash
-
Además de su número de bloque secuencial, cada bloque se identifica unívocamente por su hash. Un hash es un número hexadecimal como
0x88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6
. El hash del bloque actual no está disponible, pero proporcionando un número de bloque, puedes obtener el hash de cualquier bloque dentro de los últimos 256 bloques. difficulty
-
El nivel de dificultad de minería del bloque actual.
gaslimit
-
La cantidad máxima de gas que puede consumir este bloque. Lo establece el creador del bloque.
coinbase
-
La dirección del creador del bloque.
A continuación, abordaremos el peculiar aspecto de cómo funciona el tiempo en un contrato inteligente.
¿Qué hora es?
La mayoría de los lenguajes de programación permiten a los desarrolladores comprobar la hora, que normalmente utiliza la hora informada por el ordenador en el que se está ejecutando el programa. Por ejemplo, este programa JavaScript que se ejecuta en Node.js informa de la hora actual mientras se ejecuta:
for
(
let
i
=
0
;
i
<
10
;
i
++
)
{
console
.
log
(
new
Date
());
}
La salida de este programa sería algo así:
2019-10-05T05:08:45.058Z 2019-10-05T05:08:45.059Z 2019-10-05T05:08:45.060Z 2019-10-05T05:08:45.060Z 2019-10-05T05:08:45.060Z 2019-10-05T05:08:45.060Z 2019-10-05T05:08:45.060Z 2019-10-05T05:08:45.060Z 2019-10-05T05:08:45.060Z 2019-10-05T05:08:45.060Z
No es sorprendente que puedas ver cómo cambia el tiempo a medida que se ejecuta el programa. Aquí tienes el código equivalente en Solidity:
pragma solidity
^
0
.
5
.
0
;
contract
TimeReporter
{
event
TimeLog
(
uint256
time
);
function
reportTime
()
public
{
for
(
uint8
i
=
0
;
i
<
10
;
i
++
)
{
emit
TimeLog
(
block
.
timestamp
);
}
}
}
Los eventos emitidos por esta función tendrían todos exactamente el mismo atributo time
. Ese atributo time
, establecido por block.timestamp
, es la hora en que el bloque se añadió a la cadena de bloques. Para cada transacción de ese bloque, el atributo block.timestamp
será idéntico. Mientras que el reloj de un ordenador marca al menos una vez por milisegundo, el "reloj" de una cadena de bloques sólo marca las veces que se añaden bloques a la cadena. Debido a la baja resolución del reloj de una cadena de bloques, nunca podemos esperar que se produzca un segundo exacto. Cuando escribas código que compruebe la hora, las comparaciones siempre deben implicar mayor o menor que, en lugar de exactamente igual.
Al diseñar contratos inteligentes, también es importante tener en cuenta que los creadores de bloques pueden manipular en su beneficio el momento en que se crea un bloque, así como el orden de las transacciones. Por ejemplo, si un contrato inteligente tiene un plazo incorporado, y a la mayoría de los creadores de bloques les beneficiaría significativamente que se incumpliera ese plazo, entonces pueden optar por retrasar cualquier transacción que cumpliera el plazo. Los creadores de bloques que manipulan el orden de las transacciones en su beneficio pueden realizar transacciones de tokens "por adelantado". Por ejemplo, alguien envía una transacción para comprar 5 tokens ABC por 1 ETH, debido a alguna información nueva sobre los tokens ABC. Un creador de bloques con un poder de hash significativo podría retrasar esa transacción de 5 ABC / 1 ETH y añadir su propia transacción a un bloque por una cantidad menor y hacerse con esos tokens ABC para sí mismo. Esto sólo es posible si el creador del bloque realmente consigue crear un bloque, lo cual es altamente competitivo. Así que estas preocupaciones no son una vulnerabilidad significativa de los contratos inteligentes, pero es importante tenerlas en cuenta cuando consideres tus diseños e incentivos.
Por último, consideremos algo de lo "cripto" en criptomoneda.
Firmar transacciones
Tenemos que apreciar las razones por las que cuando "firmamos" una transacción, podemos estar seguros de que esa firma fue creada por una clave privada específica. Esto se reduce a la buena y vieja criptografía de clave pública. Una clave privada es la contrapartida secreta de una clave pública. La dirección de un EOA es un truncamiento del hash de su clave pública.3
Afortunadamente, en el desarrollo de contratos inteligentes y aplicaciones descentralizadas (DApps), no solemos trabajar directamente con claves privadas. Recomendamos encarecidamente dejar la gestión de claves privadas y firmas criptográficas a software de monedero como MetaMask. Conocer la clave privada de una EOA es sinónimo de poseer esa cuenta, porque la clave privada es lo que se utiliza para firmar las transacciones. Sin esta firma criptográfica, no hay forma de autentificar si una transacción fue realmente enviada por su EOA especificado.
Cuando enviamos una transacción Ethereum utilizando cualquiera de las bibliotecas Web3, la firma criptográfica se produce en segundo plano. Los siguientes atributos de la transacción se concatenan, se codifican y luego se firman con la clave privada configurada:
nonce
-
Número de secuencia de esta transacción para este EOA.
gasPrice
-
Cantidad de wei que esta transacción está pagando por unidad de gas.
gas
-
Cantidad de gasolina que esta transacción está dispuesta a gastar.
to
-
Dirección del destinatario de esta transacción. Puede ser un EOA o un contrato.
value
-
Cantidad de wei (si la hay) que esta transacción está enviando al destinatario.
data
-
En el caso de una llamada a un contrato, contiene el nombre de la función y todos los parámetros. En el caso de una implementación de contrato, contiene el código de bytes del contrato. Si no hay contratos implicados, suele estar en blanco.
chainId
-
Cada red pública Ethereum tiene un
chainId
. Mainnet es 1, la red de pruebas Kovan es 42, etc.
Una vez firmados esos atributos, la propia firma se incluye en la transacción para que los nodos de Ethereum puedan validar que el remitente es legítimo. Para validar esto, los nodos utilizan la dirección del remitente para validar la firma. Si alguien intentara enviar una transacción con una firma incorrecta, los nodos la rechazarían.
Resumen
En este capítulo hemos tocado ligeramente muchos aspectos de las cadenas de bloques y Ethereum. Esperamos haberte abierto el apetito y que estés deseando profundizar en el desarrollo de aplicaciones descentralizadas a través de nuestro próximo capítulo. El resto de los capítulos de la Parte I del libro son preparativos cada vez más pragmáticos para la Parte II, donde comenzaremos a desarrollar contratos inteligentes y DApps en serio.
1 Serenity sigue en desarrollo activo en el momento de escribir este artículo.
2 En el Apéndice G del documento amarillo de Ethereum, en https://ethereum.github.io/yellowpaper/paper.pdf, encontrarás un mapeo de los opcodes con los costes de gas.
3 Para profundizar en la criptografía, lee Applied Cryptography de Bruce Schneier (John Wiley & Sons).
Get Desarrollo práctico de contratos inteligentes con Solidity y Ethereum now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.