
La entrada anterior de la serie introdujo el concepto de transacción dentro de una criptomoneda. Mediante las transacciones se puede mover el dinero que existe en una cuenta a otra. Permitiendo así realizar una de las principales funciones de una moneda, transferir valor entre los usuarios. En esta entrada continúa profundizando en este concepto estudiando la validación de las transacciones. Permitiendo identificar así si una transacción es valida y cuales son las transacciones aún no gastadas en la cadena de bloques.
Transacciones no gastadas
En primer lugar, es necesario crear un nuevo objeto para registrar las transacciones no gastadas. En este objeto se ha de registrar el hash, el índice, la dirección y la cantidad. Para esto se puede utilizar el siguiente código:
class UnspentTransaction: def __init__(self, hash_id, index, address, amount): self.hash_id = hash_id self.index = index self.address = address self.amount = amount
La segunda tarea es crear una clase que contenga el listado de las transacciones no gastadas. Permitiendo de este modo disponer de todas estas en un único objeto. En nuestra implementación (el código completo de la implementación se puede encontrar en el respiratorio) se llama UnspentList
y su definición es la siguiente:
class UnspentList: def __init__(self): self.unspent = [] self.unconfirmed = [] def __spend(self, transaction): use_transactions = [] if not isinstance(transaction.inputs, datetime): for inputs in transaction.inputs: for unspent in self.unspent: if unspent.hash_id == inputs.hash_id and \ unspent.index == inputs.index: use_transactions.append(unspent) for unspent in use_transactions: self.unspent.remove(unspent) for index in range(len(transaction.outputs)): unspent = UnspentTransaction(transaction.hash_id, index, transaction.outputs[index].address, transaction.outputs[index].amount) self.unspent.append(unspent) def address_amount(self, address): amount = 0 transactions = self.address_transactions(address) for transaction in transactions: amount += transaction.amount return amount def address_transactions(self, address): result = [] for unspent in self.unspent: if unspent.address == address: result.append(unspent) for unconfirmed in self.unconfirmed: if not isinstance(unconfirmed.inputs, datetime): for unconfirmed_inputs in unconfirmed.inputs: for unspent in result: if unconfirmed_inputs.index == unspent.index and \ unconfirmed_inputs.hash_id == unspent.hash_id: result.remove(unspent) return result def append_unconfirmed(self, transaction): if self.validate_transaction(transaction): self.unconfirmed.append(transaction) return True return False def confirm_unconfirmed(self): for unconfirmed in self.unconfirmed: self.__spend(unconfirmed) self.unconfirmed = [] return True def spend_transaction(self, transaction): if self.validate_transaction(transaction): self.__spend(transaction) # Finish the spend return True return False def validate_transaction(self, transaction): if not isinstance(transaction, Transaction): return False use_transactions = [] need_validation = True if transaction.inputs is None or isinstance(transaction.inputs, datetime): need_validation = False for unconfirmed in self.unconfirmed: for unconfirmed_outputs in unconfirmed.outputs: for outputs in transaction.outputs: if unconfirmed_outputs == outputs: return False else: for inputs in transaction.inputs: for unspent in self.unspent: for unconfirmed in self.unconfirmed: for unconfirmed_inputs in unconfirmed.inputs: if unconfirmed_inputs == inputs: return False if unspent.hash_id == inputs.hash_id and \ unspent.index == inputs.index: if is_signature_valid(transaction.hash_id, transaction.signature, unspent.address): use_transactions.append(unspent) else: return False if need_validation and len(transaction.inputs) != len(use_transactions): return False total_in = 0 total_out = 0 for unspent in use_transactions: total_in += unspent.amount for output in transaction.outputs: total_out += output.amount if need_validation and total_in != total_out: return False return True
Una vez definidas estas clases se puede profundizar en el funcionamiento de estas. Inicialmente el constructor solamente crea dos listas de objetos: las transacciones que no han sido gastadas (unspent
) y las transacciones que no han sido confirmadas (unconfirmed
).
Validación de transacciones
Uno de los primeros métodos de interés en esta clase es validate_transaction
. Este método se utiliza par comprobar si una transacción es valida y agregarla a listados de transacciones sin confirmar. La primera comprobación es obvia, validar que el objeto que se ha pasado es de clase Transaction
. Posteriormente continua el resto de las validaciones que son más interesantes.
En el proceso de validación se comprueba si la clase transacción contiene operaciones de entrada. En caso de que no sea así, esta transacción puede ser la transacción de recompensa que recibe el que ha minado el bloque. Estas requieren un proceso de validación especial. Mientras que en el caso contrario será una transacción normal. Estas requieren que se compruebe
- Todas las transacciones de entrada se encuentran en la lista de transacciones no gastadas.
- Todas las transacciones de entrada están correctamente firmadas con la clave privada de la cuenta ordenante.
- La cantidad de todas las operaciones de entrada es igual a todas las operaciones de salida.
En el caso de que no se verifique la alguna de estas comprobaciones la operación no será valida. Por lo que seria rechazada a la hora de agregarla a la cadena de bloques. En caso contrario esta se podría gastar.
Nótese que tal como se ha definido en la entrada anterior la clase Transaction
todas las operaciones de entrada han de proceder de la misma cuenta. Esta es una limitación de esta implementación que podría resolver fácilmente, pero se encuentra fuera del alcance de esta implementación.
# Gastar una transacción
El método que se ha definido para gastar una transacción es spend_transaction
. Este lo que hace es comprobar que la transacción se puede gastar y llama al método interno __spend
. Este método, en el primer bucle, elimina todas las transacciones entrantes. Posteriormente, las elimina del listado de transacciones disponibles para que no puedan volverse a gastar.
Finalmente, todas las transacciones de salida son agregadas al listado de transacciones sin gastar. Por lo que los titulares de las misas las tengan disponibles.
Transacciones sin confirmar
Utilizar un bloque de la cadena de bloques para guardad una única transacción supone malgastar recursos. Los normal es que las transacciones lleguen a un nodo para validar y guardar en la cadena de bloques. Pero esto no se realiza hasta que el bloque tiene un tamaño mínimo. Mientras no se guarda en la cadena de bloques estas son las transacciones sin confirmar. Transacciones validas que se ha guardado hasta que puedan ser agregadas a la cadena de bloques.
En la clase existe un método para agregar estas transacciones (append_unconfirmed
) y otro para confirmarlas (confirm_unconfirmed
). El primero confirma que la operación es valida y la agrega a la lista de operaciones sin confirmar. En el segundo se gastan todas estas operaciones, método que se ha de llamar cuando le bloque halla sido agregado a la cadena.
Gestión de las cuentas
Además de los métodos descritos anteriormente, en esta clase se existen otros creados para obtener información de las cuentas. Estos son, uno una para obtener el saldo sin gastar disponible en una cuenta (address_amount
) y otro para obtener el listado de transacciones sin gastar de una cuenta (address_transactions
).
Conclusiones
En esta entrada se ha visto como operar con las transacciones que dan lugar a la creación de una criptomoneda. En una próxima entrada se tratará el concepto de “Wallet”, clave para la abstraer a los usuarios del concepto de transacción.
Imágenes: Pixabay (mmi9)
Deja una respuesta