Crea tu propia blockchain en Java (II): Estructuras de datos

A la hora de crear nuestra propia blockchain, uno de los puntos más importantes es definir las estructuras de datos que darán forma a nuestra cadena de bloques.

En cualquier cadena de bloques hay una serie de estructuras que siempre deben ser definidas:

  • Transacciones
  • Bloques
  • Cadena de bloques

Además de las estructuras anteriores, vamos a necesitar:

  • Un pool de transacciones donde guardar las transacciones temporalmente hasta que sean añadidas a un bloque
  • Un registro de cuentas y saldos que guarde el estado de la blockchain

blockchain

Transacciones

Las transacciones son una parte fundamental dentro de una cadena de bloques. Todo lo que rodea a la cadena de bloques está diseñado para asegurar que las transacciones puedan ser creadas, propagadas por la red, validadas y añadidas al registro o cadena de bloques.

El ciclo de vida de una transacción comenzará con la creación de la transacción, que es firmada por el emisor con su clave privada autorizando a gastar fondos de su cuenta y transmitida a un nodo de la red que la valida y propaga al resto de nodos. Finalmente la transacción es añadida a un bloque por el nodo que encuentra una solución a la prueba de trabajo y que se encarga de propagarlo al resto de nodos. Cada nodo de la red validará que el nuevo bloque es válido y lo añadirá a su cadena de bloques.

Estructura de una transacción

Las transacciones de nuestra blockchain contendrán la siguiente información:

  • Cuenta del emisor en forma de clave pública
  • Clave pública del destinatario de la transacción
  • Cantidad de fondos a transferir de la cuenta del emisor a la cuenta del destinatario
  • La firma digital que identifica al emisor y autoriza la transacción
  • Día y hora (timestamp) de creación de la transacción

Cada transacción puede ser identificada inequívocamente por el hash calculado a partir de la información descrita anteriormente. Este hash lo incluiremos también como parte de la transacción.

Clase Transaccion

Para aquellos que no sepáis que es una clase en programación orientada a objetos, una clase es un modelo que define un conjunto de variables (el estado), y métodos apropiados para operar con dichos datos (el comportamiento). A las instancias específicas de una clase con un estado concreto se les conoce como objetos. Así, la clase Transacción define las variables y métodos que toda transacción debe incluir y un objeto transacción será una instancia de la clase transacción que tiene valores específicos para las distintas variables (emisor, destinatario, cantidad, firma, etc).

A continuación se muestra el código asociado a nuestra clase Transaccion:

package com.aprendeblockchain.miblockchainenjava.commons.estructuras;

import com.aprendeblockchain.miblockchainenjava.commons.utilidades.UtilidadesFirma;
import com.google.common.primitives.Longs;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.ArrayUtils;

import java.util.Arrays;
import java.util.Date;

/*
 * La información principal en una transacción incluye:
 * 	- Hash de la transacción
 * 	- El emisor
 * 	- El destinatario
 * 	- La cantidad a ser transferida
 * 	- El timestamp de cuándo fue creada
 * 	- La firma con la clave privada del emisor
 * */

public class Transaccion {

    // Hash de la transacción e identificador único de ésta
    private byte[] hash;

    // Clave pública del emisor de la transacción
    private byte[] emisor;

    // Clave pública del destinatario de la transacción
    private byte[] destinatario;

    // Valor a ser transferido
    private double cantidad;

    // Firma con la clave privada para verificar que la transacción fue realmente enviada por el emisor
    private byte[] firma;

    // Timestamp de la creación de la transacción en milisegundos desde el 1/1/1970
    private long timestamp;

    // Constructores
    public Transaccion() {
    }

    public Transaccion(byte[] emisor, byte[] receptor, double cantidad, byte[] firma) {
        this.emisor = emisor;
        this.destinatario = receptor;
        this.cantidad = cantidad;
        this.firma = firma;
        this.timestamp = System.currentTimeMillis();
        this.hash = calcularHashTransaccion();
    }

    // Getters y setters
    public byte[] getHash() {
        return hash;
    }

    public void setHash(byte[] hash) {
        this.hash = hash;
    }

    public byte[] getEmisor() {
        return emisor;
    }

    public void setEmisor(byte[] emisor) {
        this.emisor = emisor;
    }

    public byte[] getDestinatario() {
        return destinatario;
    }

    public void setDestinatario(byte[] destinatario) {
        this.destinatario = destinatario;
    }

    public double getCantidad() {
        return cantidad;
    }

    public void setCantidad(double cantidad) {
        this.cantidad = cantidad;
    }

    public byte[] getFirma() {
        return firma;
    }

    public void setFirma(byte[] firma) {
        this.firma = firma;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    /**
     * El contenido de la transaccion que es firmado por el emisor con su clave privada
     * @return byte[] Array de bytes representando el contenido de la transaccion
     */
    public byte[] getContenidoTransaccion() {
    	byte[] contenido = ArrayUtils.addAll(String.valueOf(cantidad).getBytes());
    	contenido = ArrayUtils.addAll(contenido, emisor);
    	contenido = ArrayUtils.addAll(contenido, destinatario);
    	contenido = ArrayUtils.addAll(contenido, firma);
    	contenido = ArrayUtils.addAll(contenido, Longs.toByteArray(timestamp));        
        return contenido;
    }

    /**
     * Calcular el hash del contenido de la transacción y que pasa a ser el identificador de la transacción
     * @return Hash SHA256
     */
    public byte[] calcularHashTransaccion() {
        return DigestUtils.sha256(getContenidoTransaccion());
    }

    /**
     * Comprobar si una transacción es válida
     * @return true si tiene un hash válido y la firma es válida
     */
    public boolean esValida() {
        // verificar hash
        if (!Arrays.equals(getHash(), calcularHashTransaccion())) {
            return false;
        }
        
        // verificar firma
        try {
            if (!UtilidadesFirma.validarFirma(getContenidoTransaccion(), getFirma(), emisor)) {
                return false;
            }
        } catch (Exception e) {
            return false;
        }

        return true;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Transaccion tr = (Transaccion) o;

        return Arrays.equals(hash, tr.hash);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(hash);
    }

    @Override
    public String toString() {
        return "{" + hash + ", " + emisor + ", " + destinatario + ", " + cantidad + ", " + firma + ", " + new Date(timestamp) + "}";
    }
}

https://github.com/asuarezgrupobme/aprendeblockchain/blob/master/MiBlockchainEnJava/miblockchainenjava/src/com/aprendeblockchain/miblockchainenjava/commons/estructuras/Transaccion.java

He añadido algunas funciones extra para calcular el hash de la transacción y validar si una transacción es válida, lo cual equivale a comprobar que el hash coincide y la firma es válida. Posteriormente necesitaremos además comprobar que el emisor tiene fondos suficientes para ejecutar con éxito la transacción.

Firma y validación de transacciones

Como decíamos, el emisor firma la transacción con su clave privada. Posteriormente, cualquiera podrá verificar que la transacción ha sido firmada con esta clave privada solo conociendo la clave pública incluida en la propia transacción.

Para poder realizar esta firma y validación de firma se ha incluido una clase UtilidadesFirma con los métodos generarParClaves para generar un par de claves pública/privada, firmar usando la clave privada y validarFirma a partir de los datos y la clave pública:

package com.aprendeblockchain.miblockchainenjava.commons.utilidades;

import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public abstract class UtilidadesFirma {

	/**
	 * Factoria de claves que se inicializa con el algoritmo para generar los pares
	 * de claves publica-privada
	 */
	private static KeyFactory keyFactory = null;

	static {
		try {
			keyFactory = KeyFactory.getInstance("DSA", "SUN");
		} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
		}
	}

	/**
	 * Generar un par de claves publica-privada
	 * 
	 * @return KeyPair par de claves
	 */
	public static KeyPair generarParClaves() throws NoSuchProviderException, NoSuchAlgorithmException {
		KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA", "SUN");
		SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
		keyGen.initialize(1024, random);
		return keyGen.generateKeyPair();
	}

	/**
	 * Validar una firma para unos datos y clave publica dados
	 * 
	 * @param info         datos firmados y a ser verificados
	 * @param firma        a ser verificada
	 * @param clavePublica clave publica asociada a la clave privada con la que
	 *                     fueron firmados los datos
	 * @return true si la firma es valida para los datos y clave publica dados
	 */
	public static boolean validarFirma(byte[] info, byte[] firma, byte[] clavePublica) throws InvalidKeySpecException,
			NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
		// crear un objeto PublicKey con la clave publica dada
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(clavePublica);
		PublicKey publicKeyObj = keyFactory.generatePublic(keySpec);

		// validar firma
		Signature sig = getInstanciaSignature();
		sig.initVerify(publicKeyObj);
		sig.update(info);
		return sig.verify(firma);
	}

	/**
	 * Firmar unos datos con una clave privada dada
	 * 
	 * @param info         datos a ser firmados
	 * @param clavePrivada para firmar los datos
	 * @return firma de los datos y que puede ser verificada con los datos y la
	 *         clave pública
	 */
	public static byte[] firmar(byte[] info, byte[] clavePrivada) throws Exception {
		// crear objeto PrivateKey con la clave dada
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(clavePrivada);
		PrivateKey privateKeyObj = keyFactory.generatePrivate(keySpec);

		// firmar
		Signature sig = getInstanciaSignature();
		sig.initSign(privateKeyObj);
		sig.update(info);
		return sig.sign();
	}

	private static Signature getInstanciaSignature() throws NoSuchProviderException, NoSuchAlgorithmException {
		return Signature.getInstance("SHA1withDSA", "SUN");
	}
}

https://github.com/asuarezgrupobme/aprendeblockchain/blob/master/MiBlockchainEnJava/miblockchainenjava/src/com/aprendeblockchain/miblockchainenjava/commons/utilidades/UtilidadesFirma.java

SHA1withDSA: Algoritmo de firma DSA (Digital Signature Algorithm) con SHA-1 (Secure Hash Algorithm 1) para crear y verificar firmas digitales.

SHA1PRNG: Algoritmo para la generación de números pseudoaleatorios con SUN.

Bloques

Los bloques de la cadena se componen de una cabecera de bloque y un contenido. Cada bloque dentro de la cadena de bloques se identifica mediante el cálculo de una función hash de la cabecera del bloque. Cada bloque hace referencia a un bloque anterior  (bloque padre) incluyendo dicho hash en su cabecera. La secuencia de los hashes que unen cada bloque a su bloque padre crea una cadena que se remonta hasta el primer bloque jamás creado, conocido como el bloque génesis.

Al tener el hash de bloque anterior dentro de la cabecera del bloque, el hash del bloque anterior afecta al hash del bloque actual. La identidad propia del hijo cambia si la identidad de los padres cambia. Cuando el padre se modifica de alguna manera, los cambios de hash de los padres también cambian. Cuando el hash del padre cambia requiere un cambio en el campo hash de bloque anterior del bloque hijo. Esto a su vez hace que el hash del hijo cambie, lo que requiere un cambio en el puntero del nieto, que a su vez cambia el nieto, y así sucesivamente. Este efecto cascada asegura que, una vez que un bloque tiene muchas generaciones siguientes, no puede ser cambiado sin forzar un nuevo cálculo de todos los bloques siguientes. Debido a que un nuevo cálculo requeriría una computación enorme, la existencia de una larga cadena de bloques hace que la historia profunda de la cadena de bloques sea inmutable, que es una característica clave de la seguridad de la tecnología blockchain.

La cabecera incluye además:

  • Hash del propio bloque calculado como el hash de la cabecera
  • Dificultad para la prueba de trabajo
  • Sello temporal (timestamp)
  • Nonce o solución a la prueba de trabajo para minar dicho bloque
  • La raíz del árbol de Merkle

El contenido del bloque es básicamente la lista de transacciones añadidas al bloque.

Arboles de Merkle

Un árbol de Merkle, también conocido como un árbol hash binario, es una estructura de datos que se usa para resumir y verificar de manera eficiente la integridad de grandes conjuntos de datos. Los árboles merkle son árboles binarios que contienen hashes criptográficos.

Los árboles merkle se usan en bitcoin y otras blockchains para resumir todas las transacciones en un bloque, produciendo una huella digital completa de todo el conjunto de transacciones, proporcionando un proceso muy eficiente para verificar si una transacción está incluida en un bloque. Un árbol de Merkle se construye mediante la ejecución de una función de hash en pares de nodos de forma recursiva hasta que solo queda un único hash, al que se le llama raíz o raíz merkle.

Cuando se toman N elementos de datos, se hace hash de cada uno de ellos y se resumen en un árbol merkle, se puede comprobar si cualquier elemento de datos está incluido en el árbol con un máximo de 2*log2(N) cálculos, convirtiéndolo en una estructura de datos muy eficiente.

En el caso de la blockchain los nodos hoja o nodos en la parte inferior son las transacciones incluidas en el bloque y se genera el árbol hacia arriba aplicando la función hash correspondiente.

merkle tree

Para demostrar que una transacción específica está incluida en un bloque, un nodo solo necesita producir log2(N) hashes, elaborando una ruta de autenticación o ruta Merkle que conecte la transacción específica a la raíz del árbol. Ésto es útil por ejemplo en los conocidos como clientes ligeros o SPV que se utilizan para implementar las carteras de monedas digitales y que sólo desean guardan información relativa a las direcciones de dicha cartera y no la cadena de bloques completa.

Seria teóricamente posible crear cabeceras de bloque que contuvieran toda la información de transacciones pero esto presentaría serios problemas de escalabilidad.

Clase Bloque

A continuación se incluye la implementación de nuestra clase bloque siguiendo la definición anterior:

package com.aprendeblockchain.miblockchainenjava.commons.estructuras;

import com.google.common.primitives.Longs;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.ArrayUtils;

import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.stream.Collectors;

/*
 * Un bloque está formado por una cabecera de bloque y un contenido.
 * La cabecera incluye:
 * 	- Hash del bloque calculado a partir del contenido de la cabecera
 * 	- Hash del bloque anterior (permite mantener la cadena enlazada)
 * 	- Timestamp
 * 	- Dificultad prueba de trabajo (en nuestro caso es constante asi que no lo incluimos por ahora).
 * 	- Nonce
 * 	- Raiz arbol de merkle
 * El contenido del bloque está formado por la lista de transacciones incluidas en dicho bloque.
 * */
public class Bloque {

	// Hash del bloque e identificador único de éste. Usado para enlazar bloques.
	private byte[] hash;

	// Hash del bloque anterior.
	private byte[] hashBloqueAnterior;

	// Nonce calculado como solución a la prueba de trabajo
	private long nonce;

	// Timestamp de creación del bloque
	private long timestamp;

	// Root del arbol de merkle calculado a partir de las transacciones en el bloque.
	private byte[] raizArbolMerkle;

	// Lista de transacciones incluidas en este bloque
	private List transacciones;

	public Bloque() {
	}

	public Bloque(byte[] hashBloqueAnterior, List transacciones, long nonce) {
		this.hashBloqueAnterior = hashBloqueAnterior;
		this.transacciones = transacciones;
		this.nonce = nonce;
		this.timestamp = System.currentTimeMillis();
		this.raizArbolMerkle = calcularRaizArbolMerkle();
		this.hash = calcularHash();
	}

	public byte[] getHash() {
		return hash;
	}

	public void setHash(byte[] hash) {
		this.hash = hash;
	}

	public byte[] getHashBloqueAnterior() {
		return hashBloqueAnterior;
	}

	public void setHashBloqueAnterior(byte[] hashBloqueAnterior) {
		this.hashBloqueAnterior = hashBloqueAnterior;
	}

	public List getTransacciones() {
		return transacciones;
	}

	public void setTransactions(List transacciones) {
		this.transacciones = transacciones;
	}

	public byte[] getRaizArbolMerkle() {
		return raizArbolMerkle;
	}

	public void setRaizArbolMerkle(byte[] raizArbolMerkle) {
		this.raizArbolMerkle = raizArbolMerkle;
	}

	public long getNonce() {
		return nonce;
	}

	public void setNonce(long nonce) {
		this.nonce = nonce;
	}

	public long getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}

	/**
	 * Calcular el hash del bloque a partir de la información de la cabecera del bloque (sin transacciones)
	 * 
	 * @return Hash SHA256
	 */
	public byte[] calcularHash() {
		byte[] hashableData = ArrayUtils.addAll(hashBloqueAnterior, raizArbolMerkle);
		hashableData = ArrayUtils.addAll(hashableData, Longs.toByteArray(nonce));
		hashableData = ArrayUtils.addAll(hashableData, Longs.toByteArray(timestamp));
		return DigestUtils.sha256(hashableData);
	}

	/**
	 * Calcular la raiz del arbol de merkle formado con las transacciones
	 * @return Hash SHA256
	 */
	public byte[] calcularRaizArbolMerkle() {
		Queue<byte[]> colaHashes = new LinkedList<>(
				transacciones.stream().map(Transaccion::getHash).collect(Collectors.toList()));
		while (colaHashes.size() > 1) {
			// calcular hash a partir de dos hashes previos
			byte[] info = ArrayUtils.addAll(colaHashes.poll(), colaHashes.poll());
			// añadir a la cola
			colaHashes.add(DigestUtils.sha256(info));
		}
		return colaHashes.poll();
	}

	/**
	 * Numero de ceros al principio del hash del bloque (para la prueba de trabajo)
	 * 
	 * @return int number of leading zeros
	 */
	public int getNumeroDeCerosHash() {
		for (int i = 0; i < getHash().length; i++) {
			if (getHash()[i] != 0) {
				return i;
			}
		}
		return getHash().length;
	}

	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;

		Bloque bloque = (Bloque) o;

		return Arrays.equals(hash, bloque.hash);
	}

	@Override
	public int hashCode() {
		return Arrays.hashCode(hash);
	}

    @Override
    public String toString() {
        return "{Hash:" + hash + ", Previo:" + hashBloqueAnterior + ", RaizMerkle:" + raizArbolMerkle + ", Nonce:" + nonce + ", Timestamp:" + new Date(timestamp) + ", Transacciones:" + transacciones.toString() + "}";
    }
}

https://github.com/asuarezgrupobme/aprendeblockchain/blob/master/MiBlockchainEnJava/miblockchainenjava/src/com/aprendeblockchain/miblockchainenjava/commons/estructuras/Bloque.java

Para el calculo de la raíz del arbol de Merkle se ha seguido una implementación basada en una cola. Inicialmente se incluyen todas las transacciones en la cola y en cada iteración se consumen los dos elementos en la cabecera de la cola y se añade al final de la cola el resultado de calcular el hash de ambos elementos. Este procedimiento se produce hasta que finalmente solo queda un elemento en la cola y que corresponde a la raíz de nuestro árbol de Merkle.

Cadena de bloques y estado

Por último, tenemos que definir la estructura de nuestra cadena de bloques.

Una cadena de bloques no es más que una serie de ”bloques” que agrupan transacciones y que están enlazados siguiendo un orden temporal.

blocks_1

Podemos implementar nuestra cadena de bloques como una simple lista de bloques que se van añadiendo según son recibidos. La posición de un bloque en la cadena recibe el nombre de altura

Clase CadenaDeBloques

A continuación se incluye la definición de nuestra clase CadenaDeBloques:

package com.aprendeblockchain.miblockchainenjava.commons.estructuras;

import java.util.List;

/*
 * La cadena de bloques es esencialmente una lista de bloques enlazados ya que cada bloque tiene el identificador del bloque anterior.
 * */
public class CadenaDeBloques {

	// Lista de bloques en la cadena ordenados por altura (posición en la cadena)
	private List bloques;
	
	
	public CadenaDeBloques() {
	}

	public CadenaDeBloques(List bloques) {
		this.bloques = bloques;
	}
	
	public List getBloques() {
		return bloques;
	}

	public void setBloques(List bloques) {
		this.bloques = bloques;
	}

	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;

		CadenaDeBloques cadena = (CadenaDeBloques) o;
		
		if(bloques.size() != cadena.getBloques().size())
			return false;

		for(int i=0;i<bloques.size();i++) {
			if(bloques.get(i) != cadena.getBloques().get(i))
				return false;
		}
		
		return true;
	}

    @Override
    public String toString() {
        return bloques.toString();
    }
}

https://github.com/asuarezgrupobme/aprendeblockchain/blob/master/MiBlockchainEnJava/miblockchainenjava/src/com/aprendeblockchain/miblockchainenjava/commons/estructuras/CadenaDeBloques.java

Estado de la cadena de bloques

Si bien es cierto que las blockchains ofrecen la posibilidad de calcular el estado de la blockchain procesando todas las transacciones desde el bloque génesis, esto no es eficiente. Es por ello que además suelen guardar el estado actual de la red. Mientras que Bitcoin utiliza un sistema conocido como UTXO (Unspent Transaction Outputs), otras blockchains como Ethereum usan un sistema de cuentas y saldos usando una variante del arbol de Merkle conocido como árbol de Patricia. No voy a entrar a describir ambos modelos ya que necesitaríamos un artículo completo solo para esto pero podéis leer sobre ambos modelos en este artículo https://medium.com/@sunflora98/utxo-vs-account-balance-model-5e6470f4e0cf.

Nuestra blockchain va a tener un modelo similar al de Ethereum pero lo simplificaremos y lo que tendremos será una tabla hash que asocie claves públicas y saldos asociados a dichas claves.

Incluiremos la implementación de este estado cuando nos describamos como se propagan, validan y procesan los bloques en la red. El uso de este registro de cuentas y saldos nos permitirá entre otras cosas evitar el doble gasto.

Pool de transacciones

El pool de transacciones es el sitio en el que esperan las transacciones a ser incluidas en un bloque. Una implementación muy simple será el de una colección (Set) de transacciones. Este tipo de estructura nos permitirá evitar tener transacciones duplicadas en el pool.

A continuación se incluye la definición de nuestra clase PoolTransacciones:

package com.aprendeblockchain.miblockchainenjava.commons.estructuras;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/*
 * El pool de transacciones mantiene una colección de transacciones que están pendientes de ser incluidas en un bloque y añadidas a la cadena
 */
public class PoolTransacciones {

	private Set pool = new HashSet<>();
	
	/**
     * Añadir una transaccion al pool
     * @param transaccion Transaccion a ser añadida
     * @return true si la transaccion es válida y es añadida al pool
     */
    public synchronized boolean add(Transaccion transaccion) {
        if (transaccion.esValida()) {
        	pool.add(transaccion);
            return true;
        }
        return false;
    }

    /**
     * Eliminar una transaccion del pool
     * @param transaccion Transaccion a eliminar
     */
    public void eliminar(Transaccion transaccion) {
    	pool.remove(transaccion);
    }

    /**
     * Comprobar si el pool contiene todas las transacciones de una lista de transacciones
     * @param transacciones Lista de transacciones a comprobar
     * @return true si todas las transacciones de la coleccion están en el pool
     */
    public boolean contieneTransacciones(Collection transacciones) {
        return pool.containsAll(transacciones);
    }
}

https://github.com/asuarezgrupobme/aprendeblockchain/blob/master/MiBlockchainEnJava/miblockchainenjava/src/com/aprendeblockchain/miblockchainenjava/commons/estructuras/PoolTransacciones.java

 

Antes de añadir una transacción al pool validamos que la transacción tiene un formato válido.

En el próximo artículo…

En el siguiente artículo vamos a realizar una implementación de nuestra red de nodos que nos permita:

  • Añadir nuevos nodos a la red y que se sincronicen con el resto de nodos.
  • Propagar las transacciones y bloques en la red.
  • Guardar las transacciones temporalmente en un pool de transacciones hasta que sean añadidas a un nuevo bloque.
  • Verificar que las transacciones y bloques recibidos son válidos de acuerdo a una serie de reglas que también definiremos.

Espero que te haya gustado el artículo y ¡hasta el próximo artículo!

 

Siguiente artículo: Crea tu propia blockchain en Java (III): Red y nodos

Web construida con WordPress.com.

Subir ↑