En los días pasados, escribí una primera implementación de una blockchain, usando TDD (Test-Driven Development). Las decisiones que tomé fueron guiadas por la simplicidad: la blockchain reside en memoria, y los bloques se identifican por un número y un hash. Dos bloques que tengan el número 42 son diferentes si tienen diferentes hashes. Un bloque contiene también el hash del bloque padre (el número del padre no hace falta tenerlo, es el número del bloque hijo menos uno). El bloque génesis (el primer bloque) tiene número 0, y como hash padre tiene null. De esta manera simple, tengo todos los ingredientes para ir armando una blockchain, desde el bloque génesis en adelante, encadenando los bloques usando sus números y hashes.
Tengo una clase BlockChain (sí, prefiero usar mayúscula en la c). Hay un método para agregar un bloque, que puede devolver verdadero o falso. Sólo admite el bloque si su padre es el último bloque de la blockchain, controlando tanto número como hash esperado.
Pero existen otros nodos activos que envían otros bloques, que pueden ser agregados o no a la blockchain. ¿Cómo procesar esos bloques? Los bloques que no puedo agregar en la blockchain los agrego en otras cadenas alternativas, que llamé blockbranch:
En la segunda blockbranch de la figura, el bloque padre de su bloque 41 es todavía desconocido. Pero si llega ese bloque está todo listo para agregarlo al principio de la blockbranch. La primera blockbranch de la figura está ya conectada con la blockchain principal, compartiendo el bloque 40. Esa blockbranch no es blockchain porque tiene menos bloques que la principal. Ese es el algoritmo que elegí para decidir cuál es la blockchain: la cadena más larga obtenida, que comience desde el génesis.
Una blockbranch tienen uno o más bloques consecutivos. Esos bloques no forman parte de la blockchain actual. Pero una blockbranch puede unirse a un bloque de la blockchain o también de otra blockbranh, formando un árbol incipiente. Cada blockbranch es una proto-blockchain.
Cuando tengo suficientes bloques en una blockbranch, llegando a conectarse a otra que llegue hasta el bloque génesis, entonces esa rama es candidata a ser una blockchain. Supongamos que un nuevo bloque arriva al sistema:
El nuevo bloque es el padre del bloque 41 de la segunda blockbranch. Y resulta que su padre, un bloque 39, también es un bloque conocido, esta vez está en la blockchain principal. Entonces ahora, la segunda blockbranch ha llegado a completar una cadena de bloques desde el bloque génesis, hasta un bloque 43.
Si la rama de bloques es válida (si al aplicar los bloques al estado del final del bloque 39, se obtienen nuevos estados válidos, por ejemplo, si fuera una blockchain de criptomonedas: que en los nuevos bloques no hubiera transferencias inválidas), entonces la rama de bloques es válida. Si además, es más alta (tiene más bloques) que la blockchain principal, entonces la desplaza, y es promovida a ser blockchain, quedando:
El proceso de agregar bloques de esta manera, funciona aunque los bloques lleguen en orden diferente. Para manejar el crecimiento de la blockchain, y el formado y vigilancia de las blockbranches, y su promoción a mejor blockchain, tengo un objeto separado que llamé BlockProcessor, a cargo de toda esta orquestación. El procesador recibe nuevos bloques, y de a uno los va agregando a la blockchain, a una blockbranch, o a veces los rechaza (por ejemplo, cuando viene un bloque repetido que ya haya procesado; esto puede pasar, en el sistema final los bloques vienen de la red, y un mismo bloque puede ser enviado a nosotros desde distintos nodos). Puede detectar conexiones entre ramas y la cadena principal, y puede detectar la promoción de ramas a cadenas completas.
En el próximo post: detalles de un DSL (Domain Specific Language) que armé para diferentes escenarios de un block processor.
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez