Introducción

Qué es una DApp

Una DApp es un tipo de aplicación de Internet cuyo backend se ejecuta en una red peer-to-peer y, además, es open source. Ningún nodo de la red tiene control completo sobre el funcionamiento de la aplicación.

Dependiendo de la funcionalidad de la DApp, se pueden usar múltiples estructuras para almacenar los datos. Bitcoin es una DApp que usa una Blockchain para almacenar los datos.

Los nodos de la red pueden ser cualquier sistema conectado a Internet, por lo que detectar y prevenir quien distribuye información inválida es una tarea complicada.

Protocolos de concenso

Como no tenemos un nodo central que pueda decidir cuáles son los datos válidos o inválidos, necesitaremos un protocolo que permita a la red decidir. Actualmente existen una serie de protocolos de conceso que intentan solucionar este problema. Uno de los protocolos de conceso más populares es el Proof of Work.

Clientes de la DApp

Para usar una DApp, necesitamos un nodo conectado a la red descentralizada para poder intercambiar información con los otros nodos. Este nodo puede actuar como pasarla para interactuar con la red y proporcionar una API que otras aplicaciones consuman.

Figura 1
Figura 1. Cliente de la DApp
Los clientes de las DApps deben ser open source y su código debe ser público, sino toda la idea de la descentralización se vendría abajo.

Como desplegar un nodo puede ser una tarea complicada y no apta para usuarios no técnicos, podemos encontrar casos en los que se despliega un nodo que da servicio a múltiple clientes para facilitarles el acceso a la DApp.

Aplicación descentralizada != Aplicación distribuida

Ventajas y desventajas de una DApp

Ventajas
  • Son tolerantes a fallos ya que no existe un punto único de fallo porque las aplicaciones descentralizadas son aplicaciones distribuidas.

  • Evitan la censura, ya que no existe una autoridad central a la que un gobierno pueda hacer presión para que la ejerza.

  • No pueden ser bloqueadas, ya que no dependen de una única dirección IP. La única opción sería intentar localizar todos los nodos de la red y desconectarlos uno por uno, algo bastante complicado si la red es lo suficientemente grande y se encuentra lo suficientemente distribuida.

  • Ya que no existe una autoridad que controle la red, es más fácil depositar la confianza en ella.

Inconvenientes
  • La corrección de bugs o la actualización son complicadas ya que todos los nodos de la red deben actualizar su software.

  • Algunas aplicaciones requieren que el usuario verifique su identidad y no existe una autoridad central que pueda realizar la verificación.

  • Son más complicadas de desarrollar debido a los protocolos de consenso y a que deben estar muy bien diseñadas para que escalen desde el primer momento.

  • No pueden depender de una aplicación de terceros si dicha aplicación es centralizada, ya que se perdería la idea de la descentralización. Por ejemplo, no pueden depende de una API. Aún así, pueden depende de otras DApps.

Autenticación en las DApps

A veces es necesario la funcionalidad de las cuentas de usuarios en una aplicación. Los datos asociados a un usuario sólamente debería poder ser modificados por éste. Las DApps no pueden funcionar mediante un sistema de usuario y contraseña tradicional como hacen las aplicaciones centralizadas.

Existen diferentes formas de implementar cuentas de usuarios en DApps, aunque la más utilizada es usando un sistema de clave pública-privada. Un hash de la clave pública podría ser el ID de la cuenta de usuario. Para realizar un cambio en unos datos asociados a una cuenta de usuario, la instrucción de cambiar los datos debe firmarse utilizando la clave privada del usuario.

Si un usuario pierde su clave privada, pierde para siempre el acceso a su cuenta.

Oráculos

Una DApp no debería depender de una aplicación centralizada ya que supone un punto único de fallo, pero en algunos casos no queda otra opción si queremos obtener datos como precios, información sobre el tiempo o números aleatorios.

Para acceder a estos datos necesitaríamos lo que se conoce como un oráculo, que es el encargado de proveer estos datos. El oráculo actúa como un intermediario de confianza. Un oráculo puede obtener los datos desde varias fuentes y realizar comprobaciones para validar el dato.

En Ethereum podemos utilizar Oraclize que proporciona pruebas mediante TLSNotary de que los datos son válidos.

Divisa interna

Para que una aplicación sea sostenible debe ser viable económicamente para que el propietario pueda seguir desarrollándola. Las DApps no tienen un propietario, sin embargo, los nodos que corren el software necesitan hardware y otros recursos para poder funcionar, esto quiere decir que estos nodos deben obtener algún beneficio por el hecho de estar ejecutando el software. Aquí es donde entra en juego las divisas internas.

La mayoría de las DApps cuentan con una divisa interna y es el protocolo de consenso quien decide cuánto se lleva cada nodo. Dependiendo del protocolo de concenso puede ser que sólo un tipo determinado de nodo consiga esta divisa.

En el caso de Bitcoin, los mineros son los nodos que mantienen la red segura y funcionando y son aquellos que reciben la recompensa. Los nodos "normales" no reciben nada.

Lo siguiente que podríamos preguntarnos es: ¿Para qué nos puede servir esta divisa? Según nos dice la economía, cualquier cosa que tenga una demanda tiene un valor asociado. Si los usuarios deben pagar por usar la DApp, la demanda de la divisa crecerá y el valor de ésta también. Si la cantidad de divisa que se genera está limitada entonces su valor aumentará por la falta de ésta.

La única desventaja de este sistema es que las DApps no son gratuitas. Aquí las aplicaciones centralizadas tienen la ventaja de poder ofrecer un servicio gratis a cambio de publicidad y servicios premium.

DApps con autorización

Hasta hora hemos estado hablando de DApps que son abiertas, es decir, que pueden ser usadas por cualquiera. Por otra parte, existen DApps que necesitan autorización para ser usadas, son conocidas como Permissioned DApps.

Estas DApps tienen todas las características de las DApps abiertas, pero para entrar en la red se requiere autorización. Las DApps con autorización no usan los mismos protocolos de concenso que las DApps abiertas y no tienen una divisa interna.

Algunas aplicaciones descentralizadas

Blockchain

Una Blockchain es una base de datos distribuida, formada por cadenas de bloques diseñadas para evitar su modificación una vez que un dato ha sido publicado. Se usa un sellado de tiempo confiable y cada bloque que se añade se enlaza al bloque anterior.

Es adecuada para almacenar de forma creciente datos ordenados en el tiempo y sin posibilidad de modificación ni revisión.

Figura 2
Figura 2. Blockchain

Un bloque contiene información referente a los datos que se han almacenado durante un periodo y un número aleatorio llamado nonce. El bloque es minado y añadido a la cadena cuando se encuentra un hash del mismo que cumple unas condiciones impuestas.

El proceso de minado se basa en variar el valor del nonce hasta que el hash del bloque cumpla la condición impuesta. Un minero que encuentra el hash de un bloque es recompensado por la red por hacer el trabajo.

En el caso de Bitcoin se exige que el hash calculado sea menor que un valor target que es elegido por los nodos de la red para ajustar la dificultad de minar un bloque. Esto se realiza para intentar que la diferencia de tiempo entre el minado de bloques sea de aproximadamente 10 minutos.

Cada bloque que se añade lleva el hash del bloque anterior. Este sistema provoca que un mínimo cambio que se produzca en un bloque provocaría que ese bloque y todos los que vienen a continuación queden inmediatamente invalidados, pues los hashes de los bloques siguientes serían incorrectos. Esta comprobación es muy sencilla de efectuar para un ordenador, por lo que es muy fácil verificar la integridad de la cadena de bloques.

Ejemplo de bloque en Bitcoin

Número mágico

Siempre es 0xD9B4BEF9

4 bytes

Tamaño de bloque

Número de bytes que faltan hasta el final del bloque

4 bytes

Versión del bloque

Se incrementa con las actualizaciones

4 bytes

Hash del bloque anterior

Hash de 256-bit correspondiente al bloque anterior

32 bytes

Hash de todas las transacciones del bloque

Hash de 256-bit de la raíz del árbol de Merkle

32 bytes

Timestamp

Segundos desde 1970-01-01T00:00 UTC

4 bytes

Target

Es el valor máximo que el hash calculado puede tener

4 bytes

Nonce

Número de 32-bit que puede tomar cualquier valor

4 bytes

Contador de transacciones

Número de transacciones

1 - 9 bytes

Transacciones

Lista de transacciones

Depende del número de transacciones

Los nodos de una DApp pueden usar una cadena de bloques para almacenar los datos y compartirlo con el resto de los nodos. Cada nodo debe verificar que la cadena de bloques recibida sea correcta para aceptarla.

La clave de este sistema es que es muy difícil generar una cadena de bloques y muy fácil comprobar que es correcta. Esto hace que cualquier nodo pueda verificar fácilmente la integridad y que un atacante lo tenga muy complicado para generar una cadena de bloques falsa.

Ethereum

Figura 03

Ethereum es una plataforma descentralizada (al igual que Bitcoin), pero su objetivo en lugar de crear un libro de cuentas es servir como Backend para otras DApps. La plataforma permite la ejecución de programas conocidos como Smart Contracts que se programan usando el lenguaje de programación Solidity.

Ethereum usa una Blockchain para almacenar los datos de los Smart Contracts y utiliza proof-of-work como protocolo de concenso.

Los Smart Contracts tienen las siguientes propiedades:

  • Se encuentran en funcionamiento prácticamente el 100% del tiempo, ya que para que dejasen de funcionar debería pararse completamente toda la red de Ethereum.

  • No hay censura posible, ya que no pueden ser detenidos.

  • Su comportamiento está definido en código, por lo que un contrato no puede incumplirse (lo que no quiere decir que no puedan tener bugs).

Solidity es el lenguaje de programación oficial, pero existen otros lenguajes.

Ethereum cuenta con una divisa interna llamada Ether que permite la ejecución de estos Smart Contracts. La ejecución se ha de pagar con Ethers como si éstos fuesen la gasolina que alimenta a los Smart Contracts. La cantidad de Ether que se usa para la ejecución de los Smart Contracts se conoce como gas. Este gas va destinado a los mineros que ejecutan el código.

Al igual que en POO creamos instancias de una clase, en Ethereum creamos instancias de un Smart Contract. El proceso de crear una instancia y almacenarla en la Blockchain se conoce como desplegar un Smart Contract y también cuesta gas, puesto que estamos almacenando código en la Blockchain. Los Smart Contracts tienen métodos al igual que las clases y podemos llamarlos para ejecutar su código.

No todas las llamadas a métodos de los Smart Contracts requieren gas. Por ejemplo, cualquier operación de sólo lectura no necesita modificar ningún valor en la Blockhain, por lo que es gratis e instantáneo.

Cuando se despliega un Smart Contract, a éste se le asigna una dirección al igual que una instancia de una clase tiene una dirección en memoria RAM. Para interactuar con un contrato desplegado necesitamos conocer su dirección y su ABI.

La ABI de un Smart Contract es la información de cómo se debe llamar a un método, esto es la ID del método, los parámetros de entrada y el valor de retorno. En Ethereum es posible enviar datos (Payload) en una transacción, lo que permite (conociendo la ABI del Smart Contract), llamar a sus métodos.

Además de las direcciones de los Smart Contracts existen las direcciones de usuarios. La dirección de un usuario no contiene código que pueda ser ejecutado. Tanto una dirección de un usuario como de un Smart Contract tiene un balance de Ethers que pueden enviarse y recibirse.

No confundir el Ether enviado en una transacción con el gas que cuesta realizar la transacción. Por ejemplo, para llamar a un método de un contrato no hace falta enviar ethers (el valor de la transacción es 0), pero habría que pagar el gas correspondiente a su ejecución.

Cuentas de usuario

A diferencia de los Smart Contracts, las direcciones de las cuentas de usuario tienen asociada una clave pública y una clave privada. Un usuario que quiera realizar una transacción generará una clave privada y a partir de ésta obtendrá su clave pública correspondiente. La dirección de la cuenta de usuario se obtiene a partir de un hash de la clave pública.

Gas

El gas es la unidad de medida de la computación requerida para cada instrucción en la EVM. Al realizar una transacción es necesario especificar la cantidad máxima de gas que estamos dispuestos a pagar. Si la cantidad de gas consumida es menor o igual que este límite, la transacción será minada correctamente, en caso contrario sería abortada y sus cambios revertidos.

Aunque una transacción sea abortada por haber llegado al límite de gas especificado, el minero se llevará su parte ya que la computación se ha llevado a cabo y han consumido recursos.

Otro valor a especificar en una transacción es la cantidad de ethers que queremos pagar por cada unidad de gas. Cuánto más alto sea el precio, más probable será que un minero acepte la transacción y sea minada antes. Transacciones con precios bajos tardarán más en ser minadas o incluso pueden no ser minadas.

Transacciones

Una transacción es un dato que una dirección envía a otra y ha sido firmado por la fuente. En Ethereum el algoritmo de firmado que se usa es el ECDSA (Elliptic Curve Digital Signature Algorithm) que es un algoritmo basado en curvas elípticas. Una trasacción contiene los siguientes datos:

  • Destinatario: Dirección del destinatario de la transacción. Puede ser una cuenta de usuario o un Smart Contract.

  • Valor: Cantidad de ethers que se envían en la transacción. Puede ser 0.

  • Límite de gas: Si la transacción require una catidad de gas superior al límite indicado sería abortada. Cuánto más compleja sea la ejecución de un método, más gas consumirá su llamada.

  • Precio del gas: Cantidad de ethers a pagar por cada unidad de gas. Las transacciones mejor pagadas serán minadas antes, ya que los mineros ganarían más con ellas.

  • Nonce: Contador que se incrementa para evitar posibles ataques de replay.

  • Payload: Datos que podemos enviar en la transacción. También influyen en el gas total a pagar.

  • v, r y s: Parámetros que definen la firma digital. A partir de ellos se puede obtener la clave pública y por lo tanto la dirección de la cuenta (por eso la dirección de origen no aparece en la transacción).

Ethereum Virtual Machine (EVM)

EVM es el entorno donde se ejecuta el byte-code de Ethereum. Cada nodo de la red ejecuta una instancia de la EVM. Todos los nodos ejecutan todas las transacciones que se realizan a un Smart Contract utilizando la EVM, por lo que todos los nodos ejecutan exactamente los mismos cálculos y almacenan los mismos valores.

Las transacciones que no ejecutan código de un Smart Contract también requiren ser procesadas para comprobar que el balance de la cuenta ordenante es suficiente.

Esta redundancia es útil ya que permite a cualquier nodo almacenar los resultados de las ejecuciones de los Smart Contracts para así tener el valor y almacenarlo.

Cada transacción requiere capacidad de computación y almacenamiento por lo que estas transacciones deben de tener un coste, de lo contrario cualquiera podría inundar la red. Además, si las transacciones fuesen gratis, los mineros no tendrían incentivo para incluir las transacciones en los bloques y minarían bloques vacíos. Como cada transacción require una cantidad diferente de computación también consumirar una cantidad diferente de gas.

Forks

Los forks son situaciones que ocurren en una blockchain cuando se produce un conflicto entre diferentes nodos sobre cuál es la blockchain legítima. Cuando se produce un fork parte de los mineros minarán una blockchain y otra parte minará otra. Existen tres tipos de forks:

  • Forks ordinarios: Ocurren de forma natural el la blockchain cuando dos mineros minan un bloque casi al mismo tiempo. En el caso de Ethereum existe un sistema para tratar estos bloques conocidos como uncles.

  • Soft forks: Ocurre cuando se producen cambios en el código fuente que requiere que más del 50% de la capacidad computacional de la red los apoyen. Por ejemplo, un soft fork se produce cuando se modifica el código para que anule una serie de bloques o transacciones. Basta con que la mayoría de los mineros apliquen el cambio para que la otra parte los acepte.

  • Hard fork: Ocurre cuando se producen cambios en el código fuente que requiere que toda la red los acepten. Un hard fork se produce cuando se cambia la estructura del bloque o se cambia la recompensa que se ofrece a los mineros.

Smart Contracts

Geth

Antes de comenzar el desarrollo debemos ejecutar un nodo de Ethereum que nos permita la comunicación con la red y desplegar contratos. Actualmente existen varios clientes de Ethereum que se pueden utilizar:

  • geth: Es el cliente oficial desarrollado por la fundación Ethereum. Está desarrollado con Go. Es el que usaremos en este workshop.

  • parity: Es el segundo cliente más usado. Desarrollado con Rust por Gavin Wood, uno de los fundadores de Ethereum. Es quizá el cliente más completo, ya que es también un navegador de DApps.

  • cpp-ethereum: La tercera implementación más usada, escrita en C++.

Existen otras implementaciones menos usadas en multitud de lenguajes de programación como JavaScript, Java, Python, Haskell, etc.

Instalación

Tenemos varias opciones para instalar Geth. Puesto que está escrito en Go basta con descargarnos el binario adecuado para nuestra plataforma. También podemos usar Docker para ejecutarlo. Otra opción sería compilarlo desde las fuentes.

Podemos ejecutar Geth usando Docker con el siguiente comando:

Ejecutar geth con Docker
docker run -it -p 30303:30303 ethereum/client-go --syncmode=fast

Geth usará los llamados bootnodes para descubrir nodos en la red. Estos nodos son especiales ya que sólo se encargan de compartir otros nodos y no de almacenar la cadena. Al poco de iniciar comenzará a descargar la blockchain de Ethereum al completo, lo cual es un proceso largo y requiere varios gigabytes disponibles para almacenar toda la cadena. Además, desplegar un contrato e interactuar con ellos requiere disponer con una cuenta con ethers para poder pagar a los mineros.

Usando el parámetro --syncmode=fast no se computan los estados de la blockchain sino que ya se descargan computados, lo que hace que sea más rápida la sincronización.

Afortunadamente existen varias redes de Ethereum actualmente además de la red principal (main net). Estas redes alternativas conocidas como test nets nos permiten desarrollar sin tener que pagar dinero real. Las tres test nets más conocidas son:

  • Ropsten

  • Kovan

  • Rinkeby

Ejemplo 1. Usar Geth con Ropsten

Para conectarnos a una red testing tenemos que indicarle a geth otros bootnodes manualmente:

docker run --rm -it ethereum/client-go --testnet --syncmode=fast --bootnodes "enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303,enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303"

Puppeth

También existe la posibilidad de crear nuestra propia red privada de Ethereum. Puppeth es una utilidad que nos ayuda a crear nuestras propias redes privadas.

Haciendo una analogía con un sistema de integración continua tendríamos:

  • Red privada: Entorno de desarrollo

  • TestNet: Entorno de testing

  • MainNet: Entorno de producción

Lo primero es generar el bloque génesis de la red, que es donde se define un estado inicial de la red, por ejemplo, pueden existir cuentas con ethers desde el primer momento. Puppeth es un CLI que podemos encontrar en las imágenes de docker etiquetadas como alltools.

Para crear una red privada puede no ser conveniente usar proof-of-work como protocolo de concenso ya que consume muchos recursos. Como alternativa tenemos el proof-of-authority que se basa en que sólo unas direcciones especificadas en el bloque génesis tienen permisos para crear bloques.

El primer paso es generar una dirección de la siguiente forma:

Generar una dirección
mkdir workshop; cd workshop
docker run --rm -it -v $(pwd):/data ethereum/client-go:alltools-latest geth --datadir /data account new

Una vez introducida la contraseña, nos generará la dirección que se mostrará por pantalla. Ahora ejecutaremos puppeth:

Como se puede ver, hemos creado la cuenta antes de inicializar la blockchain. Esto es porque una cuenta no es más que un par de claves pública y privada, por lo que a una dirección le corresponde una clave privada independientemente de la blockchain. No es recomendable usar en la blockchain principal direcciones que ya hayamos usado en redes privadas o de testing.
Ejecutar puppeth
docker run --rm -it -v $(pwd):/data ethereum/client-go:alltools-latest puppeth

Puppeth nos pedirá un nombre para nuestra red privada, podemos poner cualquier cosa, por ejemplo "workshop". A continuación mostrará un menú donde tenemos que elegir la opción 2. Configure new genesis. Seguiremos los siguientes pasos:

  1. Which consensus engine to use?: 2. Clique - proof-of-authority

  2. How many seconds should blocks take?: 2

  3. Which accounts are allowed to seal?: Aquí introducimos la dirección que generamos antes.

  4. Which accounts should be pre-funded?: Aquí también introducimos la dirección que generamos antes.

  5. Specify your chain/network ID if you want an explicit one: random

  6. Anything fun to embed into the genesis block?: ``

Una vez generado, volveremos al menú y seleccionaremos la opción 2. Manage existing genesis. Y luego 2. Export genesis configuration. En el nombre del fichero introduciremos: /data/workshop.json y ya tendremos nuestro bloque génesis generado.

Inicializar una blockchain usando el bloque génesis generado
docker run --rm -it -v $(pwd):/data ethereum/client-go:alltools-latest geth --datadir /data init /data/workshop.json

Ahora podemos iniciar un nodo en nuestra red privada:

Iniciar un nodo en la blockchain privada
docker run --rm -it -v $(pwd):/data -p 8545:8545 ethereum/client-go:alltools-latest geth --networkid 39475 --rpc --rpcaddr 0.0.0.0 --datadir /data console
El networkid si lo generamos aleatoriamente podemos encontrarlo en el fichero workshop.json.

Una vez veamos la consola, procedemos a desbloquear la cuenta autorizada y comenzar a minar:

Comenzar proceso de minado
personal.unlockAccount("0x39091b5f009f21b0dead7320d9487842b0655e12", "password", 0)
miner.start()

Ganache

Crear una red privada es un proceso relativamente complejo, por ello existe una utilidad que nos permite simular una red privada sólo para tests y depuración de contratos: Ganache.

Esta herramienta se comporta como un cliente de Ethereum (como geth) ofreciendo la misma API, pero funciona de forma más rápida ya que no hay que esperar los 15 segundos, los bloques se minan instantáneamente y, por supuesto, sin ningún coste. Su funcinamiento es local y no depende de una conexión a internet.

Anteriormente, ganache era conocida como testrpc.

Para instalar ganache ejecutamos el siguiente comando:

Instalar ganache-cli
npm install -g ganache-cli

Para iniciar la simulación de una red ejecutamos el siguiente comando:

Iniciar red de pruebas
ganache-cli

También podemos pasarle una clave privada y una cantidad de ethers para que ganache genere la cuenta automáticamente:

Iniciar red de pruebas con una cuenta específica
ganache-cli --account="0x54f773ff404a530d7426daf071c098617eb34d54dbe1a198354049125243948a,10000000000000000000"

ganache-cli es la versión para consola, también podemos usar ganache que tiene una interfaz gráfica. Podemos descargarlo desde http://truffleframework.com/ganache/.

Infura

Además de desplegar nuestro propio nodo, existen nodos públicos a los que podemos conectarnos. Los nodos públicos están fuera de nuestro control, aunque muchas veces son convenientes porque nos ahorra tener un equipo en constante sincronización.

Infura provee de una serie de nodos públicos a los que podemos conectarnos. Cuando usamos Metamask, realmente estamos usando un nodo de Infura, aunque podemos configurar Metamask para que use el nodo que queramos.

figure05
Figura 3. Diagrama de Infura

Cuando usamos un nodo, podemos tener nuestras cuentas (claves privadas) almacenadas en el propio nodo. En el caso de los nodos públicos, esta funcionalidad está desactivada, puesto que no tendría sentido que los usuarios de los nodos públicos almacenasen su clave privada en ellos.

A la hora de firmar transacciones debemos hacerlo de forma local y, una vez firmadas, podemos usar el nodo público para enviarla a la red.

Hay que tener muchísimo cuidado si tenemos un nodo funcionando, puesto que este nodo no debería exponer nunca el puerto donde se encuentra escuchando la API. Si algún atacante tiene acceso a nuestro nodo puede intentar robarnos ether en el momento de desbloquear la cuenta.

Truffle

Configuración

Truffle es un framework que nos permite automatizar ciertas tareas de desarrollo. Para crear un nuevo proyecto con Truffle ejecutamos lo siguiente:

Crear un nuevo proyecto con Truffle
mkdir -p workshop/truffle
cd workshop/truffle
truffle init

Tendremos la siguiente estructura dentro de nuestro directorio workshop:

  • build: Contiene la ABI de los contratos. La ABI se almacena en un fichero con formato JSON. Usaremos la ABI para interactuar con nuestro contrato.

  • contracts: Contiene el código fuente de los contratos que vamos a desarrollar.

  • migrations: Contiene las migraciones. Las migraciones son scripts que se ejecutan cuando actualizamos nuestro contratos.

  • test: Contiene los tests para nuestros contratos.

  • truffle-config.js / truffle.js: Contiene la configuración para el despligue de los contratos. Podemos borrar truffle-config.js.

Creamos un fichero llamado package.json que contenga lo siguiente:

package.json
{
  "name": "erc20-example",
  "version": "1.0.0",
  "description": "Basic ERC20 Token",
  "main": "truffle.js",
  "scripts": {
    "test": "truffle test",
    "compile": "truffle compile",
    "deploy:dev": "truffle migrate --network development",
    "deploy:test": "truffle migrate --network test"
  },
  "dependencies": {
    "ethereumjs-wallet": "^0.6.0",
    "truffle-wallet-provider": "0.0.5",
    "web3": "^1.0.0-beta.30"
  },
  "devDependencies": {
    "babel-eslint": "^8.2.2",
    "eslint": "^4.18.1",
    "eslint-config-airbnb-base": "^12.1.0",
    "eslint-plugin-html": "^4.0.2",
    "eslint-plugin-import": "^2.9.0"
  }
}

Por último, el fichero truffle.js debe quedar de las siguiente forma:

truffle.js
const Wallet = require('ethereumjs-wallet');
const WalletProvider = require('truffle-wallet-provider');
const Web3 = require('web3');

const PRIVATE_KEY = '';
const NODE_URL = '';

module.exports = {
  networks: {
    development: {
      host: '127.0.0.1',
      port: 8545,
      network_id: '*'
    },
    test: {
      provider: new WalletProvider(
        Wallet.fromPrivateKey(Buffer.from(PRIVATE_KEY, 'hex')),
        NODE_URL
      ),
      gas: 4600000,
      gasPrice: Web3.utils.toWei('20', 'gwei'),
      network_id: '3'
    }
  }
};

Para instalar las dependencias ejecutamos:

Instalar dependencias
npm install

Deberíamos tener la siguiente estructura de directorios:

├── build
│   └── contracts
│       └── Migrations.json
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── node_modules
├── test
├── package.json
└── truffle.js

Compilar contratos

Vamos a crear un nuevo contrato en la carpeta contracts. Lo llamaremos HelloWorld.sol y tendrá el siguiente contenido:

Contrato Hello World
pragma solidity ^0.4.11;


contract HelloWorld {
    function greet() public pure returns (string) {
        return "Mi primer contrato en Solidity";
    }
}

Para compilar un contrato (generar su ABI) ejecutamos:

Compilar un Smart Contract
npm run compile

Tras compilar un contrato, encontraremos los ficheros compilados dentro del directorio build/contracts.

Crear migraciones

Las migraciones nos ayudan con la actualización de contratos. Al crear un proyecto con Truffle, automáticamente se genera un Smart Contract llamado Migrations.sol que lleva la cuenta de los despliegues que hacemos, de forma que no desplegará dos veces el mismo contrato. También se incluye una migración llamada 1_initial_migration.js que se encarga de desplegar el contrato Migrations.sol.

Cada vez que modificamos un contrato y queremos desplegar una nueva versión, debemos crear una nueva migración en el directorio migrations.

Para desplegar nuestro contrato HelloWorld.sol debemos crear una nueva migración en el directorio migrations. Debe contener lo siguiente:

2_hello_world_deploy.js
var HelloWorld = artifacts.require('./HelloWorld.sol');

module.exports = function(deployer) {
  deployer.deploy(HelloWorld);
};

Desplegar contratos

Desplegar un Smart Contract en nuestra red de desarrollo:

Desplegar un Smart Contract
npm run deploy:dev
Para que funcione el despliegue de un Smart Contract debemos tener ganache-cli funcionando mientras realizamos el despliegue.

Para desplegar en una testnet, necesitamos un nodo que esté conectado a la ella y esté sincronizado. Una forma de evitar disponer de un nodo sincronizado (que puede tardar horas) es usar Infura. Con Infura disponemos nodos públicos que nos dan acceso a diferentes redes de Ethereum, como la red principal o las testnets.

Para usar Infura con truffle, editamos el fichero truffle.js y modificamos la variable NODE_URL:

truffle.js
const NODE_URL = 'https://ropsten.infura.io';

De esta forma usaremos un nodo de Infura para desplegar contratos en la red Ropsten. Para iniciar el despliegue ejecutamos:

Desplegar un Smart Contract
npm run deploy:test

Tokens ERC20

ERC20 es un estándar que permite desarrollar tokens de Ethereum con una interfaz común. Esto permite que sean compatibles con multitud de aplicaciones. La interfaz se define de la siguiente forma:

erc20.sol
contract ERC20Basic {
    event Transfer(address indexed from, address indexed to, uint256 value);

    function totalSupply() public view returns (uint256);
    function balanceOf(address who) public view returns (uint256);
    function transfer(address to, uint256 value) public returns (bool);
}

Una posible implementación de un Token ERC20 sería la siguiente:

pragma solidity ^0.4.20;

import "./ERC20Basic.sol";


contract BasicToken is ERC20Basic {
    // Evento que se ejecuta cuando se realiza una compra. Se emite en el evento
    // la dirección de quien ha comprado tokens y la cantidad.
    event Buy(address buyer, uint256 amount);

    // Nombre del token
    string public name = "Colmena Token";

    // Símbolo mostrado en algunas aplicaciones
    string public symbol = "CTKN";

    // Número de decimales que soporta el token
    uint8 public decimals = 18;

    // Precio en wei de cada token
    uint256 public price;

    // Se almacena el saldo de cada cuenta (address) de Ethereum
    mapping(address => uint256) private balances;

    // Tokens que existen actualmente
    uint256 private totalSupply_;

    /**
     * Constructor del token. Se ejecuta sólamente en el momento que se
     * depliega en la red.
     *
     * @param _price  uint256  Precio del token
     */
    function BasicToken(uint256 _price) public {
        // Existen 0 tokens en el momento de la creación
        totalSupply_ = 0;
        price = _price;
    }

    /**
     * Muestra la cantidad de tokens que existen actualmente.
     *
     * @return uint256 La cantidad existente de tokens
     */
    function totalSupply() public view returns (uint256) {
        return totalSupply_;
    }

    /**
     * Muestra el saldo de una cuenta
     *
     * @param  _owner   address  Cuenta de la que se comprueba su saldo
     * @return          uint256  Balance de la cuenta en wei
     */
    function balanceOf(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }

    /**
     * Método para comprar tokens. Se calcula la cantidad de tokens en función
     * de los ethers enviados al método.
     *
     * @return bool  True si se ha producido correctamente la compra
     */
    function buy() public payable returns (bool) {
        require(msg.value > 0);

        // Importante comprobar que el precio no sea cero porque provocaría
        // una división por cero.
        assert(price != 0);
        uint256 amount = msg.value / price;

        balances[msg.sender] = balances[msg.sender] + amount;
        totalSupply_ = totalSupply_ + amount;

        emit Buy(msg.sender, amount);

        return true;
    }

    /**
     * Método para transferir tokens a otra dirección de Ethereum. No se puede
     * enviar tokens a la dirección cero.
     *
     * @param  _to     address  Dirección a la que se envían token
     * @param  _value  uint256  Cantidad de tokens a enviar
     * @return         bool     True si se ejecuta correctamente
     */
    function transfer(address _to, uint256 _value) public returns (bool) {
        require(_to != address(0));
        require(_value <= balances[msg.sender]);

        balances[msg.sender] = balances[msg.sender] - _value;
        balances[_to] = balances[_to] + _value;

        emit Transfer(msg.sender, _to, _value);

        return true;
    }
}

Frontend

Una vez tenemos un contrato desplegado en una red, el siguiente paso es realizar una interfaz gráfica para el mismo. Se puede hacer todo tipo de interfaces, sin embargo, en esta guía veremos como hacer una interfaz web.

web3.js

web3.js se trata de una librería para JavaScript que nos permite interactuar con los contratos desplegados. Con esta librería podemos conectarnos a un nodo de Ethereum y usar la JSON-RPC.

Existen dos posibilidades para conectarnos a un nodo:

  • Conexión directa: Se realiza una conexión a un nodo del cual conocemos su dirección IP. Es recomendable permitir la elección del nodo al cual conectarse, ya que esta decisión corresponde al usuario de la aplicación y no al desarrollador.

  • Navegador compatible con web3: Podemos usar el nodo configurado en el propio navegador por el usuario. Para ello, el navegador debe ser compatible con web3, por ejemplo Mist es un navegador compatible. También podemos usar Metamask si usamos Firefox o Chrome.

Una DApp puede elegir usar uno de los dos métodos o los dos. La forma recomendada es mediante un navegador compatible con web3, ya que permite al elegir el nodo de forma global para todas las DApps.

figure04
Figura 4. Modos de conexión

Existen dos versiones de web3.js. La versión actual y la beta de la versión 1.0. En esta guía se usará la versión 1.0 beta.

Creación de proyecto

Usaremos Vue.js como framework para desarrollar nuestra interfaz web. Para crear nuestro proyecto, usaremos vue-cli.

En esta guía no veremos cómo funciona Vue.js, aunque lo usaremos durante toda la sección de frontend. Puesto que el proyecto de ejemplo es bastante simple no es necesario tener mucho conocimiento sobre el framework. Se ha elegido Vue.js por su simplicidad y por ser fácil de aprender.

Primero debemos instalar vue-cli:

Instalar vue-cli
npm install -g vue-cli

Una vez instalado, entramos en la carpeta creada anteriormente e iniciamos el proyecto:

Crear proyecto con Vue.js
vue init webpack erc20-webui

El comando anterior pedirá una serie de parámetros que podemos rellenar de la siguiente forma:

Configuración con vue-cli

? Project name webui

? Project description ERC20 Token WebUI

? Author Tu nombre aquí

? Vue build standalone

? Install vue-router? No

? Use ESLint to lint your code? No

? Set up unit tests No

? Setup e2e tests with Nightwatch? No

? Should we run npm install for you after the project has been created? (recommended) npm

Estructura de un proyecto Vue
.
├── build
│   ├── build.js
│   ├── check-versions.js
│   ├── logo.png
│   ├── utils.js
│   ├── vue-loader.conf.js
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   └── webpack.prod.conf.js
├── config
│   ├── dev.env.js
│   ├── index.js
│   └── prod.env.js
├── index.html
├── package.json
├── package-lock.json
├── README.md
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   └── main.js
└── static

A continuación eliminamos el fichero src/components/HelloWorld.vue y editamos el fichero src/App.vue para que quede de la siguiente forma:

src/App.vue
<template>
<div id="app">
  Mi primera aplicación con Vue.js
</div>
</template>

<script>
export default {
  name: 'App',
};
</script>

Para ejecutar un servidor de pruebas en local, ejecutamos el siguiente comando:

Iniciar servidor de pruebas
npm run dev

CC-BY-SA