Codificación de cadena de bloques: ¡Los muchos lenguajes diferentes que necesitas!

Rajarshi Mitra

9 months ago
Blockchain Coding: The Many different Languages You Need!
en flag
de flag

La tecnología de cadena de bloques es increíblemente fascinante. No sería exagerado pensar en un futuro el cual esté completamente creado sobre esta. Entonces, ¿qué necesitas aprender para empezar a desarrollar en la cadena de bloques? ¿Qué lenguajes te darán una ventaja? En esta guía, pasaremos por los lenguajes más importantes. Y si solo estás empezando revisa nuestros cursos integrales de la cadena de bloques.

 

Problemas con el desarrollo de software de cadena de bloques

Antes de empezar, revisemos algunos de los desafíos a los que se enfrenta un desarrollador de cadena de bloques. Crear y mantener una cadena de bloques pública no es sencillo por varias razones.

 

(Antes de continuar, se agradece públicamente a David Schwartz por su presentación con respecto al uso de C++ en el desarrollo de software de cadena de bloques en CPPCON 2016.)

Codificación de cadena de bloques: ¡Los muchos lenguajes diferentes que necesitas!

 

Razón #1: Seguridad

Las cadenas de bloques, como David Schwartz lo expone, deben ser fortalezas. Primero, el código es público y abierto para que todos lo vean. Cualquiera puede ver el código y revisar errores y vulnerabilidades. Sin embargo, a diferencia de otros recursos de código abierto, la desventaja de encontrar vulnerabilidades en el código de cadena de bloques es masiva. Cualquier programador puede hackear y potencialmente llevarse millones y millones de dólares. Debido a estas legítimas preocupaciones de seguridad, el desarrollo en la cadena de bloques usualmente es muy lento.

Razón #2: Gestión de recursos

Es muy importante llevar un ritmo con la red. No puedes quedarte muy atrás y no estar al día con todas las demandas de la red. Debes estar bien equipado para manejar consultas remotas y locales.

Razón #3: Rendimiento

La cadena de bloques siempre debe funcionar en sus capacidades más altas posibles, pero para que esto ocurra el lenguaje elegido debe ser extremadamente versátil. El tema es que existen ciertas tareas en la cadena de bloques que pueden ser paralelizadas mientras hay algunas tareas que no pueden hacerse en paralelo.

Un buen ejemplo de una tarea “paralelizable” es la verificación de una firma digital. Todo lo que necesitas para la verificación de firma es la clave, la transacción y la firma. Con solo tres datos puedes realizar verificaciones de manera paralelizada.

Sin embargo, no todas las funciones en la cadena de bloques se deberían hacer de esa manera. Piensa en la ejecución misma de la transacción. No se pueden ejecutar múltiples transacciones en paralelo; necesita hacerse una a la vez para evitar errores como gastos dobles. Algunos lenguajes son buenos en operaciones paralelas mientras que otros lo son en operaciones no paralelas.

Razón #4: Aislamiento

¿Qué es un comportamiento determinístico?

  • Si A + B = C, entonces sin importar las circunstancias, A+B siempre será igual a C. Eso se llama comportamiento determinístico.
  • Las funciones hash son determinísticas, lo que significa que el hash de A siempre será H(A).

 

Entonces, en el desarrollo de cadena de bloques, todas las operaciones de transacciones deben ser determinísticas. No puedes tener una transacción que se comporta de una forma y luego de otra al día siguiente. De manera similar, no puedes tener contratos inteligentes que funcionan de dos maneras diferentes en dos máquinas diferentes.

La única solución a esto es el aislamiento. Básicamente, aislar tus contratos inteligentes y transacciones de elementos no determinísticos.

Así que, hemos discutido los problemas principales que los desarrolladores de cadena de bloques enfrentan. Ahora revisemos algunos de los lenguajes que los desarrolladores pueden usar para programar en la cadena de bloques.

 

Lenguaje #1: C++

Antes que nada, empecemos con el abuelo de todos, el perdurable C++. C++ fue creado por Bjarne Stroustrup como una extensión al lenguaje C. El Lenguaje fue diseñado para tener la flexibilidad y eficiencia de C pero con algunas diferencias importantes. La diferencia más grande entre C y C++ es que mientras C está orientado a procesos, C++ es orientado a objetos.

Lo que significa esto es que, en C++, los datos y funciones están encapsulados en pequeños paquetes llamados “objetos”, lo que significa que una vez que se crea un objeto, se puede llamar fácilmente y ser reusado en otros programas, que reduce grandemente el tiempo de programación.

Veamos el programa de C++ más sencillo del mundo. El programa “Hola Mundo”:

[CODE SNIPPET]

Este código mostrará: Hello World!

Entonces, ¿por qué aún se usa C++ para programar? Seguramente hay lenguajes mucho más glamorosos hoy en día, ¿por qué la gente aún insiste en volver a C++? ¿Por qué la cadena de bloques de Bitcoin está codificada en C++?

Bien, en realidad, C++ tiene ciertas características que lo hacen muy atractivo. (Un agradecimiento público a Peter Wiulle y David Schwartz por la siguiente explicación).

 

Característica #1: Control de memoria

¿Recuerdas lo que dijimos antes sobre los desafíos del desarrollo de cadena de bloques? Las cadenas de bloques no solo deberían ser fortalezas aseguradas sino también tener una gestión de recursos efectiva. Se supone que una cadena de bloques interactúe con demasiados extremos que no sean de confianza, al mismo tiempo que brinde un servicio rápido a todos los nodos.

Este servicio rápido es crítico para el éxito de una criptomoneda como Bitcoin. Recuerda, todos están basados en el principio de “consenso”, todos los nodos en la red deben aceptar y rechazar la misma cantidad de bloques, o de lo contrario podría haber una bifurcación en la cadena.

Para satisfacer todas estas demandas y funcionar al máximo nivel, necesitas un control estricto y completo sobre el uso del CPU y la memoria. C++ brinda eso a sus usuarios.

 

Característica #2: Paralelización

Como lo hablamos antes, uno de los desafíos principales de la programación de cadena de bloques es la integración de tareas que paralelicen bien y de las que no lo hagan. La mayoría de los lenguajes se especializan en una, sin embargo, la capacidad de paralelización de C++ es lo suficientemente buena para manejar tareas tanto paralelas como no paralelas. Un hilo es un conjunto de instrucciones que pueden ejecutarse simultáneamente. C++ no solo permite una espectacular estructura con una comunicación entre hilos efectiva, sino también optimiza el rendimiento con un único hilo.

 Característica #3: Semánticas de movimiento

Uno de los aspectos más interesantes de C++ son sus semánticas de movimiento. Las semánticas de movimiento brindan una manera de mover los contenidos entre objetos en lugar de copiarlos. Revisemos las diferencias entre las semánticas de copia y las de movimiento. (Los siguientes datos se tomaron de la respuesta de Peter Alexander en “StackOverflow”).

Semánticas de copia:

  • assert(b == c);
  • a = b;
  • assert(a == b && b == c);

Entonces, ¿qué ocurre aquí? El valor de b va hacia a y b permanece sin cambios al final de todo.

Ahora, considera esto.

Semánticas de movimiento:

  • assert( b = = c);
  • move (a,b);
  • assert (a = =c );

¿Qué ocurre aquí?

¿Puedes notar la diferencia entre los dos bloques de código?

Cuando usamos las semánticas de movimiento, el valor de “b” no necesita ser cambiado.  Esa es la diferencia entre las semánticas de copia y las de movimiento. La ventaja más grande de las semánticas de movimiento es que puedes obtener copias de ciertos datos solo cuando los necesites, que reduce grandemente la redundancia en el código y da un enorme aumento de rendimiento. Así que como puedes ver, esta gestión de memoria eficiente y alto rendimiento son deseables para la cadena de bloques.

 

Característica #4: Polimorfismo en tiempo de compilación

¿Qué es el polimorfismo?

¿Recuerdas cuando llamamos a C++ un “lenguaje de programación orientada a objetos (OOP)”? El polimorfismo es, casualmente, una propiedad de OOP. Usando el polimorfismo, usas una característica particular en más de una manera. En C++, el polimorfismo puede usarse en dos maneras:

  • Polimorfismo en tiempo de compilación.
  • Polimorfismo en tiempo de ejecución.

En esta guía, solo nos centraremos en el polimorfismo en tiempo de compilación. Existen dos maneras en que C++ implementa el polimorfismo en tiempo de compilación:

  • Sobrecarga de función.
  • Sobrecarga de operador.

 

Sobrecarga de función:

La sobrecarga de función ocurre cuando tienes muchas funciones del mismo nombre pero con diferentes parámetros de entrada.

Considera este programa:

[CODE SNIPPET]

Ahora cuando ejecutes esta función la salida será:

  • 2
  • 2.65
  • 7

Así que, como puedes ver, la misma función func() se usó de 3 maneras diferentes.

Sobrecarga de operador:

En C++ el mismo operador puede tener más de un significado.

  • Es decir, el “+” puede usarse tanto como para la adición matemática como para la concatenación.
  • La concatenación básicamente significa tomar dos cadenas y combinarlas en una.
  • Entonces 3+4 = 7.

Y

  • Block+geeks = Blockgeeks.
  • El mismo operador, hizo dos funciones diferentes, este operador tiene sobrecarga.

El Polimorfismo en tiempo de compilación ayuda mucho en el desarrollo de cadena de bloques. Ayuda a poner las responsabilidades separadas en varias funciones y, a su vez, aumentar el rendimiento de todo el sistema.

Característica #5: Aislamiento de código

C++ tiene características de espacio de nombres que pueden importarse de un programa a otro. El espacio de nombres ayuda a evitar colisiones de nombres. Además, dado que C++ tiene clases, puede actuar como límites entre varias APIs y ayudar a hacer una separación más clara.

Una clase en C++ es una estructura de tipos o datos definida por el usuario declarada con la palabra clave class que tiene datos y funciones como miembros. Puedes acceder a las funciones declaradas en la clase declarando objetos de esa clase particular.

Característica #6: Madurez

El lenguaje es maduro y es actualizado regularmente. Existen al menos 3 compiladores sólidos, como dice David Schwartz, y las características nuevas están pensadas para resolver problemas reales. Existen herramientas de depuración y análisis de todo tipo disponibles para todo, desde creación de perfiles de rendimiento hasta detección automática de problemas de todo tipo. Esto significa que el languaje está creciendo constantemente para incorporar funciones mejores y nuevas.

Por las características anteriores, Satoshi Nakamoto escogió a C++ como el lenguaje base del código fuente de Bitcoin.

 

Lenguaje #2: Javascript

 

Luego tenemos a JavaScript.

Junto con HTML y CSS es una de las tres tecnologías principales en la Producción de contenido de la World Wide Web. JavaScript usualmente es usado para crear páginas web altamente interactivas. Entonces, ahora veremos cómo crear una cadena de bloques muy sencilla usando JavaScript. Un gran agradecimiento público a savjee.be por el contenido en esta sección.

Supone que queremos crear una cadena de bloques sencilla en JavaScript. Antes de hacerlo, hay ciertas cosas que necesitamos tratar.

 

¿Qué es una cadena de bloques y exactamente cómo funciona en el código?

Una cadena de bloques básicamente es una cadena de bloques que contiene datos. Básicamente es una lista enlazada glorificada. Sin embargo, ¿qué la hace tan especial? Una cadena de bloques es inmutable. Lo que significa que, una vez que ingresan los datos a un bloque, no se pueden cambiar nunca más. ¿Cómo alcanza la inmutabilidad una cadena de bloques? Es por un mecanismo sencillo pero ingenioso llamado “hashing”. Revisa el diagrama a continuación:

Blockchain Coding: The Many different Languages You Need!

Imagen cortesía de: Lauri Hartikka – Artículo de Medium

Cada bloque está conectado al bloque anterior a través de un puntero hash que contiene el hash del bloque anterior. Entonces, ¿cómo hace inmutable a la cadena?

Una de la propiedades más fascinantes de las funciones hash criptográficas es que incluso al cambiar la entrada un poco, esto puede afectar grandemente el hash de salida. Por ejemplo, mira esto:

Blockchain Coding: The Many different Languages You Need!

Con solo cambiar la primera “T” de mayúsculas a minúsculas cambió drásticamente el hash de salida.

 

Entonces, ¿cómo afecta esto a la cadena de bloques?

 

Cada bloque está conectado al anterior a través de un puntero hash. Entonces, si alguien fuera a manipular los datos en un bloque, cambiaría drásticamente el hash y como resultado, terminaría afectando toda la cadena (dado que todos los bloques están enlazados). Esto congelaría la cadena lo que es una imposibilidad y por lo tanto los bloques permanecen sin cambios.

Entonces, ¿cómo creamos un bloque? ¿De qué consiste un bloque simple? En nuestra criptomoneda sencilla que vamos a crear (Llamémosla “BlockGeeksCoin”), cada bloque tendrá las siguiente información:

  • Índice:  Para conocer el número de bloque.
  • Marca de tiempo:  Para conocer la hora de creación.
  • Datos:  Los datos dentro del bloque.
  • Hash anterior: El hash del bloque anterior.
  • Hash: El Hash del bloque actual.

 

Antes de continuar. Necesitas entender ciertos términos que vamos a usar en nuestro programa:

  • this:  La palabra clave “this” es invocada dentro de una función y te permite acceder al valor dentro de un objeto específico que llame esa función particular.

 

  • Constructor: Un constructor es una función especial que puede ayudar a crear e inicializar un objeto dentro de una clase. Cada clase está restringida a solo un constructor.

Ahora que eso está hecho, empecemos a crear nuestro bloque.

 

Creando el bloque

[CODE SNIPPET]

Análisis del código

Ok, entonces aquí hay un bloque. En la primera línea del código, llamamos a la biblioteca crypto-js porque la función de hash sha256 no está disponible en JavaScript.

Luego, invocamos al constructor dentro de la clase para llamar a objetos que tendrán ciertos valores. La parte que probablemente llame tu atención es la función calculateHash(). Veamos exactamente lo que está haciendo.

En un bloque, tomamos todos los contenidos y hacemos hash con estos para obtener el hash de este bloque en particular. Estamos usando la función JSON.stringify para convertir los datos del bloque en una cadena y hacer el hash.

Ok, ya tenemos el bloque completado y listo para continuar. Ahora conectemos los bloques en una cadena de bloques.

Creando la cadena de bloques

[CODE SNIPPET]

Análisis del código

Ok, están ocurriendo muchas cosas en la cadena anterior, dividamos esto en secciones.

 

Sección 1: El bloque génesis

¿Qué es el bloque génesis?

El bloque génesis es el primer bloque de la cadena de bloques, y la razón por la que es especial es porque, mientras cada bloque apunta a su bloque anterior, el bloque génesis no apunta a nada.  Entonces, en el momento que sea crea una nueva cadena, el bloque génesis se invoca inmediatamente. Además, puedes ver una función “createGenesisBlock()” donde dimos manualmente los datos del bloque:

 

[CODE SNIPPET]

Ahora que construimos el bloque génesis, creemos el resto de la cadena.

 

Sección 2: Agregando bloques

Primero, necesitamos saber cuál es el último bloque en la cadena de bloques. Para eso usamos la función getLatestBlock().

[CODE SNIPPET]

Entonces, ¿qué ocurre aquí? ¿Cómo estamos agregando los bloques? ¿Cómo estamos revisando si el bloque dado es válido o no?

¿Recuerdas los contenidos de un bloque?

Un bloque tiene el hash del bloque anterior ¿verdad?

 

Entonces, lo que haremos aquí es sencillo. Comparar el valor del hash anterior del nuevo bloque con el valor de hash del último bloque.

Blockchain Coding: The Many different Languages You Need!

Imagen cortesía de: Lauri Hartikka – Artículo de Medium

Si estos dos valores coinciden, significa que el nuevo bloque es válido y se agrega a la cadena de bloques.

 

Sección 3: Validando la cadena

 

Ahora, necesitamos revisar que nadie ha estado alterando nuestra cadena de bloques y que todo está estable.

Estamos usando el ciclo “for” desde el bloque 1 hasta el último bloque. El bloque génesis es el bloque 0.

[CODE SNIPPET]

Si el “previousHash” del bloque actual no es igual al “Hash” del bloque actual, entonces esta función devolverá False, de lo contrario devolverá True.

 

Usando la cadena de bloques

Ahora, finalmente vamos a usar la cadena de bloques para crear nuestro BlockGeeksCoin.

  • let BlockGeeksCoin = new Blockchain();
  • BlockGeeksCoin.addBlock(new Block(1, “20/07/2017”, { amount: 4 }));
  • BlockGeeksCoin.addBlock(new Block(2, “20/07/2017”, { amount: 8 }));

 

¡Y eso es todo!

¿Entonces qué ocurre aquí?

Creamos una nueva criptomoneda basada en la cadena de bloques y la nombramos BlockGeeksCoin. Invocando este nuevo objeto, activé el constructor, que a su vez creó automáticamente el bloque génesis.

Simplemente agregamos dos bloques más a esta y le dimos algunos datos.

Es así de sencillo.

(Gracias savjee.be por la explicación asombrosa y sencilla.)

 

Lenguaje #3: Python

Guido van Rossum, un programador holandés, creó Python en 1991. Python está basado en una filosofía simple: Simplicidad y minimalismo Una de las maneras más notables que incorporaron la simplicidad en su lenguaje es usando espacios en blanco para implicar bloques de código en lugar de llaves o palabras clave. Veamos lo que significa esto.

 

Revisemos un programa sencillo de “hola mundo”.

 

print(‘Hello, world!’)

 

Sí, ¡eso es todo!

Compara esto al programa “hola mundo” de C++.

¿Ves qué tan simple es en comparación?  ¿Qué te parece si hacemos algo un poco más complicado? Digamos que sumamos dos números y mostramos el resultado.

[CODE SNIPPET]

Y eso es todo.

La salida de este programa será:

  • The sum of 1.5 and 6.3 is 7.8.

Entonces, subamos la apuesta. ¿Cómo vamos a programar una cadena de bloques completa usando Python? Los siguientes datos y código fueron tomados del artículo de Gerald Nash en Medium.

 

Creando el bloque

Primero que nada, creemos nuestro bloque:

import hashlib as hasher

[CODE SNIPPET]

Análisis del código

Empezamos importando la biblioteca hash para usar las funciones de hash SHA 256 (tal como en JavaScript).

Al igual que antes, el bloque tiene el mismo valor:

  • Índice.
  • Marca de tiempo.
  • Datos.
  • Hash anterior.
  • Hash.

 

Una vez más, estamos completando los valores de hash mediante una función, al igual que antes.

Creando el bloque génesis

Ahora, creemos el bloque génesis:

import datetime as date

[CODE SNIPPET]

Análisis del código

Importamos datetime para colocar la marca de tiempo.

Simplemente generamos el bloque génesis y le dimos algunos datos manualmente para usar. El valor del hash anterior es “0” porque no está apuntando a ningún otro bloque.

Creando el resto de los bloques

Ahora definamos cómo se crearán los siguientes bloques.

[CODE SNIPPET]

Análisis del código

Entonces, ¿vamos a determinar los valores de cada parte de los datos dentro de cada uno de los bloques?

El índice de bloque es simplemente el índice del último bloque + 1.

La marca de tiempo es la fecha y hora actual.

Los datos del bloque son un mensaje sencillo: “Hey! I’m block <número de índice>”.

Al hash lo calculamos usando la función que definimos antes.

Y por último, estamos devolviendo estos valores al bloque.

Creando la cadena de bloques

Finalmente, creemos la cadena de bloques.

[CODE SNIPPET]

Análisis del código

Primero, creamos el bloque génesis y le damos su valor a “previous_block”.

Luego determinamos cuántos bloques agregar, en este ejemplo vamos a usar 15.

Entonces ejecutamos un ciclo que va hasta 15 y agrega cada bloque a la cadena de bloques. Al final del ciclo estamos mostrando qué número de bloque se ha agregado a la cadena de bloques mostrando su número de índice. Además, estamos mostrando el Hash.

Así se vería la salida:

Blockchain Coding: The Many different Languages You Need!

Imagen cortesía de: Artículo de Gerald Nash en Medium

Obviamente tanto aquí como en JavaScript puedes agregar características más complicadas como la Prueba de trabajo. Si quieres aprender cómo implementar eso es altamente recomendable que visites el artículo de Gerald Nash. Pero por ahora, al menos sabes cómo crear una cadena de bloques sencilla en Python.

 

Lenguaje #4: Solidity

Finalmente, llegamos a Solidity. Para cualquiera que quiera aprender cómo desarrollar DAPPs (Aplicaciones descentralizadas) o entrar en el juego de las ICO, es absolutamente necesario aprender Solidity. Ya hemos detallado una guía de esto que puedes leer aquí. Sin embargo, te vamos a dar un resumen básico. Solidity fue desarrollado por Gavin Wood, Christian Reitwiessner, Alex Beregszaszi, Yoichi Hirai y varios contribuidores pasados de Ethereum para permitir la creación de contratos inteligentes en plataformas de cadena de bloques como Ethereum.

Solidity es un lenguaje deliberadamente compacto, débilmente tipado con una sintaxis similar a ECMAScript (JavaScript). Existen algunos puntos claves a recordar del Documento de diseño de fundamentos de Ethereum, es decir, concretamente que estamos trabajando en un modelo de pila y memoria con un tamaño de palabra de instrucción de 32 bytes, la EVM (Máquina virtual de Ethereum) nos da acceso al programa “pila” que es como un espacio de registro donde también podemos pegar direcciones de memoria para crear bucles/saltos del Contador de programa (para control de programa secuencial), una “memoria” temporal expandible y un “almacenamiento” más permanente que en realidad es escrito en la cadena de bloques permanente, y principalmente, la EVM requiere un determinismo total dentro de los contratos inteligentes.

Entonces, antes de continuar, revisemos un ejemplo básico de contrato en Solidity. (Código tomado de GitHub).

Ejecutemos un ciclo while sencillo en Solidity:

[CODE SNIPPET]

Entonces, analicemos.

Sección 1: Asignar valores

En el primer paso estamos llenando un arreglo llamado “integers” que toma 10 enteros de 8-bit sin signo. Hacemos esto mediante un ciclo while. Veamos lo que ocurre dentro del ciclo while.

[CODE SNIPPET]

Recuerda, ya hemos asignado el valor de “0” al entero x. El ciclo while va desde 0 hasta integers.length. Integers.length es una función que devuelve la capacidad máxima de un arreglo. Entonces, si decidimos que un arreglo tendrá 10 enteros, arrayname.length devolverá el valor de 10. En el ciclo anterior, el valor de x va desde 0 – 9 (<10) y asigna el valor de sí mismo al arreglo de enteros. Entonces, al final del ciclo, los enteros tendrán los siguientes valores:

 

0,1,2,3,4,5,6,7,8,9.

 

Sección 2: Sumar el contenido del arreglo

Dentro de la función getSum() vamos a sumar los contenidos del arreglo mismo. Lo haremos repitiendo el mismo ciclo while anterior y usando la variable “sum” para sumar los contenidos del arreglo.

Sección 3: Terminar el contrato

Esta función finaliza el contrato y envía los fondos restantes en el contrato al creador del contrato.

Cuando se le preguntó sobre la inspiración y motivación detrás de la creación de Solidity, Dr. Gavin Woods contestó:

 

“Este [Solidity] fue pensado para ser una herramienta sofisticada para desarrollar contratos que a la larga dé tanto a los desarrolladores como a los usuarios buena información sobre lo que hace el código. Para ayudar con esto, he concebido a NatSpec, un formato de documentación intuitivo para contratos, y lo hice un ciudadano de primera clase en Solidity. También propuse un subconjunto formal de lenguaje de prueba (aún no implementado) para maximizar los tipos de garantías de exactitud que puedan realizarse.

Incorporé a los eventos como ciudadanos de primera clase en el lenguaje Solidity para brindar una buena abstracción para LOGs similar en forma a las llamadas de funciones. La inspiración de esto vino de las “señales” del sistema de metaobjetos de Qt.

Una función posterior que Christian R. y yo pensamos juntos fueron los modificadores de funciones; que permite atributos colocados como parte de la firma de una función para hacer algunas modificaciones al aparente cuerpo de la función. Siendo un medio de expresión muy declarativo, es un idioma que cae muy bien en el espacio de programación orientada a contratos.”

Codificación de cadena de bloques: Conclusión

En este artículo solo cubrimos 4 languajes para la programación de cadena de bloques que se usan para desarrollar la cadena de bloques. En realidad existen muchos otros lenguajes que puedes usar (Java, Go). Si eres un programador, entonces las posiblidades para ti son verdaderamente infinitas. A medida que el mundo se vuelva cada vez más descentralizado y la cadena de bloques se vuelva más popular, tu futuro es definitivamente ilimitado.

 

Like what you read? Give us one like or share it to your friends

0
0
Hungry for knowledge?
New guides and courses each week
Looking to invest?
Market data, analysis, and reports
Just curious?
A community of blockchain experts to help

Get started today and earn 4 bonus blocks

Already have an account? Sign In