Guia para construir una blockchain. Parte 10

el estado de la cadena

En el post anterior, implementamos un código de programa que validó la integridad
de transacciones. Hubo una omisión significativa en este código, no abordamos la cuestión de cómo se obtienen los valores no gastados de una transacción anterior, y tampoco abordamos la cuestión de la validación de los valores no gastados en transacciones anteriores. El motivo de esta omisión es que los valores no gastados se implementan en un registro de pares valores-clave. En este post, corregiremos esta deficiencia implementando un registro de pares valor-clave para las transacciones. También implementaremos un registro de pares valor-clave que nos permitirá determinar el bloque que contiene una transacción en particular.
En resumen, un registro de pares clave-valor es una estructura de datos que asigna claves a valores. Dada una clave, podemos obtener el valor asociado a ella. Las claves del registro son únicas. El beneficio principal y significativo de los pares valor clave es que
proporcionan acceso aleatorio al registro. Por lo tanto, obtener, editar y eliminar pares valores clave es extremadamente rápido.
En Bitcoin, el registro de pares valores clave de los valores de transacción no gastadas se implementa como una base de datos de LevelDB. Blancioin también usa LevelDB en su implementación.

LevelDB es particularmente un registro simple de pares clave-valor . Es así, deliberadamente por diseño. El objetivo del diseño de LevelDB es implementar un almacenamiento persistente muy simple y muy rápido que tenga una confiabilidad extrema.

encontrando valores de transaccion no gastadas

En Bitcoin y Blancoin, los bloques se almacenan en el disco como archivos de texto normales. Los bloques no son almacenado en una base de datos. Esta es una característica de diseño deliberada. El objetivo de diseño en Bitcoin es mantener la implementación de la criptomoneda al nivel más simple posible. Este enfoque maximiza la confiabilidad. Simplemente almacenando bloques en el disco como archivos binarios ordinarios, se convierte en la responsabilidad del sistema operativo garantizar que los bloques se escriban correctamente en disco. Además, el sistema operativo debe asegurarse de que estos archivos no estén dañados.
La blockchain solo necesita examinar la respuesta del sistema operativo para escribir un bloque en el disco. Hay una razón adicional para la existencia de ésta implementación de almacenamiento basado en bloque. En Bitcoin y Blancoin, todas las bases de datos y
otras estructuras de datos necesarias se pueden construir desde cero a partir de estos archivos de bloque. El único punto de fallo es, por lo tanto, que cualquier archivo de bloque falte o este dañado. Ya que una blockchain es una estructura inmutable, los archivos de bloque también deben ser inmutables y solo se deben escribir una vez y leer
muchas veces. Es principalmente responsabilidad del sistema operativo garantizar
la integridad de los archivos de bloque. Esta implementación de almacenamiento en bloque es significativamente menos compleja que almacenar bloques en una base de datos SQL o NoSQL. Sin embargo, existe una deficiencia intrínseca en este modelo de almacenamiento de bloques basado en archivos.

Consideremos una transacción en la que queremos gastar un valor en alguna transacción anterior. Supongamos además que solo tenemos el ID de transacción correspondiente a este valor. Nosotros tendremos que recuperar la transacción del bloque que contiene esta transacción y luego determinar si el valor no se ha gastado. Dado que los bloques no están indexados, esto implicará que tendremos que buscar en todos los archivos de bloque para encontrar esta transacción en particular y luego buscar una transacción posterior que ha gastado este valor. Si, por ejemplo, la blockchain esta compuesta por diez millones de archivos de bloque, entonces es evidentemente que tal búsqueda será inaceptablemente lenta. En una aplicación del mundo real, puede haber miles de transacciones por segundo.
Una mejora en blockchain puede resolver este problema de escalabilidad manteniendo metadatos de las transacciones en un registro de pares valores-clave. Cada vez que ocurre una transacción, la blockchain guarda los metadatos relacionados con la transacción en el registro de pares valores clave y también persiste estos datos al disco. Ahora, si se necesita interrogar a la blockchain sobre una transacción, buscamos en el registro de pares valores-clave y, dado que dicha blockchain proporciona acceso, la respuesta será más o menos instantánea. Vamos a construir una implementacion de base de datos LevelDB que proporciona tal capacidad para esa tasa de transacciones y una segunda base de datos LevelDB que nos permite consultar el bloque que contiene una transacción en particular.

diseño de la base de datos (chainstate)

Llamaremos Chainstate a la base de datos de LevelDB que proporciona la capacidad de consultar el estado de las outputs de las transacciones no gastadas. La Chainstate nos permitirá determinar si existe una transacción en particular y si el valor de esta transacción no esta gastada. Además, Chainstate proporcionará otra metainformación
que es útil para el procesamiento de transacciones. Una base de datos de Chainstate también es importante para la construcción de billeteras. Igual que Bitcoin, las redes de criptomonedas no mantienen una cuenta para cada entidad que realiza transacciones. En cambio, la red de criptomonedas mantiene un libro mayor distribuido que registra
transacciones a medida que ocurren. Los clientes que utilizan dicha criptomoneda deben mantener sus propias wallets (carteras) para realizar transacciones. Dicha wallet contendrá la información de los pares criptográficos del usuario y los valores (utxos) no gastados disponibles en direcciones públicas que el titular de la wallet ha creado. Una wallet proporcionará un historial de transacciones que tiene el titular de la billetera. Finalmente, una wallet también permitirá al usuario crear pares criptográficos para usar en las transacciones. Notarás que en ausencia de dicha wallet, el cliente tendría que
mantener una copia local de toda la cadena de bloques y consultar la cadena de bloques cada vez que se quiera participar en una transacción. Lo siguiente es la estructura de registros para la base de datos de Chainstate:

key: "transaction_index" + "_" + "vout_index"
value: {
"pkhash": <string>
"value": <int>
"spent": <bool>
"tx_chain": <string>
}

La clave, llamada clave de transacción, es la concatenación de un ID de transacción y un índice en la lista de vout en el que se encuentra un valor de interés. Estas dos piezas de información es suficiente para identificar un valor en una transacción anterior. Debido a las propiedades de los ID de transacción, se garantiza que una clave de transacción es universalmente única.

El valor asociado a una clave es un diccionario. A este valor clave lo llamamos fragmento de la transacción. Sus campos se interpretan de la siguiente manera:
value es un número positivo. Es la cantidad de centavos de blancoin que se gastan en esta transacción, a la que apunta el transactionid y el vout index en la clave de la transacción. value es un número entero. pk_hash es la cadena de la forma

"pk_hash": RIPEMD_160(SHA-256(public_key))

pk_hash se construye a partir de la dirección pública que tiene el destinatario del valor proporcionado al receptor del valor. spent es un tipo booleano. Es True si el valor se ha gastado y False en caso contrario.
tx_chain apunta a la entrada de la transacción que ha gastado esta salida. tx_chain estará vacío si la output no se ha gastado. Dado que una base de datos de LevelDB solo acepta claves y valores de cadena, debemos convertir el
diccionario de fragmentos de transacciones a una cadena antes de la inserción en la base de datos. En Python, esto se puede lograr fácilmente con la función dump en el módulo json.
Nuestra base de datos de Chainstate tendrá cinco funciones de interfaz:

open_chainstate(directory_path: "string")
close_chainstate()
put_transaction(key: "string", value: "dictionary") => "bool"
get_transaction(key: "string") => "bool or string"
transaction_update(key: "string") => "bool"

La función open_chainstate abre la base de datos de la base de datos LevelDB Chainstate LevelDB y la crea si no existe. Esta función recibe un parámetro de ruta de archivo completo o parcial, incluido el nombre de la base de datos. close_chainstate cierra la base de datos. La función get_transaction recibe una clave de transacción que tiene la forma “ID de transacción” + “_” + str (vout_index). Esta función devuelve el fragmento de transacción.
perteneciente a esta clave o False si la clave no existe en la Chainstate.
La función put_transaction agrega un par clave-valor a Chainstate. Esta función
recibe una clave de transacción y un fragmento de transacción como argumentos. Devuelve True si el par clave-valor se agrega a Chainstate y False en caso contrario. put_transaction realiza escrituras sincrónicas en el Chainstate.

La api de LevelDB espera argumentos de cadena de bytes y devuelve cadenas de bytes. Un cadena de bytes es una cadena con prefijo de longitud (a diferencia de una cadena que tiene un terminador nulo “\ 0”. Esto es necesario para garantizar la portabilidad de la transmisión a través de las redes. Estas conversiones son manejadas internamente por nuestras funciones de interfaz. La api de LevelDB también contiene funciones que permiten que los pares clave-valor se agrupen antes de que sean escritas en la blockchain; esta es una característica de rendimiento muy eficaz

La función transaction_update actualiza la base de datos de Chainstate cuando se gasta el valor de transacción. Esta función asegura que el fragmento de transacción anterior, cuyo valor se gasta, exista. Evita el “doble gasto”. Esta función marca un fragmento de transacción como gastado y establece el atributo tx_chain del fragmento de transacción que ha recibido el valor gastable. Esta función recibe una transacción como argumento.
Ahora podemos implementar código de programa para el módulo chainstate.

codigo de la base de datos chainstate

Asegúrate de haber compilado e instalado la base de datos de LevelDB. Adicionalmente,
asegúrese de haber instalado la interfaz python de LevelDB , el módulo plyvel, en su
entorno virtual de blancoin. A continuación, crearemos una base de datos levelDB Blancoin vacía. Esta base de datos estara ubicada en el subdirectorio de datos de la raíz de Blancoin. navega a la raíz de tu entorno virtual, ingrese al shell de Python y haga lo siguiente:

> (virtual) python
>>> import plyvel
>>> db = plyvel.DB('data/blancoindb/')

Si navegas al directorio de datos, verá un subdirectorio blancoindb. Este subdirectorio contiene los archivos Chainstate.

Ahora estamos listos para un tutorial del código del programa Chainstate de Blancoin .
Copie el siguiente código en un archivo llamado hchaindb.py y luego copie este archivo en el subdirectorio chainstate de la raíz.
Blancoin tiene una implementación particularmente simple para rastrear y resumir las transacciones. La función open_hchainstate recibe una ruta de archivo completa o parcial que incluye el nombre de la base de datos de LevelDB. Abre la base de datos de Blancoin Chainstate que está nombrado en la ruta y crea esta base de datos si no existe. Esta función no crea ningún subdirectorio enumerado en la ruta del archivo si no existe. Si la función tiene éxito, devuelve un identificador a la base de datos de Chainstate. En caso de falla, la función devuelve False.
La función close_hchainstate recibe un identificador para el Chainstate y cierra la base de datos de forma ordenada.
La función put_transaction recibe una clave de transacción y un fragmento de la transacción como sus argumentos. put_transaction devuelve True si tiene éxito en la inserción del id de transacción, el par clave-valor del fragmento de transacción en la base de datos de Chainstate. De lo contrario, put_transaction devuelve False.
La función get_transaction tiene un argumento de clave de transacción y devuelve el
fragmento de transacción asociado con esta clave (junto con la suma de comprobación). Si la clave de transacción es ficticia, get_transaction devuelve False.
La función update_transaction actualiza el Chainstate cuando no se han gastado anteriormente los valores. En particular, evita el doble gasto de un valor no gastado dos o
mas veces. Esta función tiene una transacción como argumento:


import hconfig
import rcrypt
import plyvel
import logging
import json
import pdb

logging.basicConfig(filename="debug.log",filemode="w", \
format='%(asctime)s:%(levelname)s:%(message)s', level=logging.DEBUG)
handle to the Helium Chainstate Database
hDB = None
def open_hchainstate(filepath: "string") -> "db handle or False":
"""
opens the Helium Chainstate key-value store and returns a handle to
it. The database will be created if it does not exist. All of the
directories in filepath must exist.
Returns a handle to the database or False
"""
try:
global hDB
hDB = plyvel.DB(filepath, create_if_missing=True)
except Exception as err:
logging.debug('open_hchainstate: exception: ' + str(err))
return False
return hDB
def close_hchainstate() -> "bool":
"""
close the Helium Chainstate store
Returns True if the database is closed, False otherwise
"""
global hDB
try:
hDB.close()
if hDB.closed != True: return False
except Exception as err:
logging.debug('close_hchainstate: exception: ' + str(err))
return False
return True
def put_transaction(txkey: "string", tx_fragment: "dictionary") -> "bool":
"""
creates a key-value pair in the Chainstate Database
Receives a transaction key and a transaction_fragment.
The transaction key is: transactionid + "_" + str(vout_index)
The tx_fragment parameter received is:
{
"pkhash":
"value":
"tx_chain"
}
Returns True if the key-value pair is created and False otherwise
"""
try:
if transaction key already exists delete because
the transaction fragment is going to be updated
encoded_key = str.encode(txkey)
hDB.delete(encoded_key)
keyvalue = json.dumps(tx_fragment)
save the txkey-fragment pair to the store
hDB.put(encoded_key, str.encode(keyvalue))
except Exception as err:
print(str(err))
logging.debug('put_transaction: exception: ' + str(err))
return False
return True
def get_transaction(key: "string") -> "False or dictionary":
"""
Receives a transaction key (transactionid + "_" + str(vout_index))
for a transaction fragment
returns False or the key value. The transaction fragment returned has
form:
{
"pkhash":
"value":
"spent":
"tx_chain"
}
"""
try:
get the transaction fragment corresponding to the transaction
key, return False if the key does not exist
fragment = hDB.get(str.encode(key))
if fragment == None:
raise(ValueError("transaction fragment not found"))
fragment = json.loads(fragment.decode())
except Exception as err:
logging.debug('get_transaction: exception: ' + str(err))
return False
return fragment
def transaction_update(trx: "transaction")-> "bool":
"""
receives a transaction. Updates the chainstate database to
reflect the transaction. Sets previous transaction fragments
to indicate that they have been spent. Updates these fragments
to indicate the transaction id of the consuming transaction.
returns True or False if there is an error
"""
try:
collect all of the outputs of previous transactions and set them
to spent
specify the transaction key consuming the previous transaction inputs
for vin in trx["vin"]:
fetch a previous transaction fragment. It must exist
prev_tx_key = vin["txid"] + "_" + str(vin["vout_index"])
tx_fragment = get_transaction(prev_tx_key)
if tx_fragment == False:
print("vin fragment not found: " + prev_tx_key)
raise(ValueError("transaction fragment not found"))
double spend error
should be detected prior to writing to the Chainstate
if tx_fragment["spent"] == True:
print("fragment is double spend error: " + prev_tx_key)
raise(ValueError("transaction fragment double spend error:
" + prev_tx_key))
set the spent values to True in the previous transaction
fragment
tx_fragment["spent"] = True
set the reference to the consuming transaction
tx_fragment["tx_chain"] = trx["transactionid"] + "_" +
str(vin["vout_index"])
save to HeliumDB
ret = put_transaction(prev_tx_key, tx_fragment)
if ret == False:
raise(ValueError("failed to update spent tx fragment"))
put the fragments of the consuming transaction into the HeliumDB
ctr = 0
for vout in trx["vout"]:
tx_fragment = {}
txkey = trx["transactionid"] + "_" + str(ctr)
tx_fragment["pkhash"] = vout["ScriptPubKey"][2]
tx_fragment["value"] = vout["value"]
tx_fragment["spent"] = False
tx_fragment["tx_chain"] = ""
if put_transaction(txkey, tx_fragment) == False:
raise(ValueError("failed to insert consuming transaction
fragment"))
ctr += 1
except Exception as err:
print(str(err))
logging.debug('transaction_update: exception: ' + str(err))
return False
return True


pytest para la chainstate

Copie el siguiente código de prueba unitaria en el archivo test_hchaindb.py y guárdelo en el directorio unit_tests. Diríjete al directorio unit_tests y ejecuta las pruebas en el entorno de blancoin:

> (virtual): pytest test_hchaindb.py -s

La función setup_module en test_hchaindb.py abre la base de datos de Chainstate
antes de ejecutar cualquier prueba. El teardown_module cierra la base de datos después de que las pruebas han sido ejecutadas. Hay dos funciones auxiliares que generan un par de transacciones adyacentes sintéticas aleatorias. Hay pruebas para put_transaction, get_transaction, y la funcionalidad transaction_update. La función put_transaction prueba varios valores aleatorios para pkhash, value y spent. Con get_transaction, intentamos obtener el valor de una clave de transacción inexistente. La funcion update_transaction prueba la creación de utxos no gastadas y prueba que el doble gasto no puede ocurrir.
Cuando ejecutsa estas pruebas, deberías ver que pasan seis pruebas:

"""
pytest unit tests for the hchaindb module
"""
import hchaindb as chain
import rcrypt
import pytest
import json
import random
import secrets
import pdb
import os
def setup_module():
assert bool(chain.open_hchainstate("heliumdb")) == True
def teardown_module():
assert chain.close_hchainstate() == True
if os.path.isfile("heliumdb"):
os.remove("heliumdb")
#
Globals for a synthetic previous transaction
#
prev_tx = None
prev_tx_keys = []
def make_keys():
"""
makes a private-public key pair for the synthetic
previous transaction
"""
prev_tx_keys.clear()
key_pair = rcrypt.make_ecc_keys()
prev_tx_keys.append([key_pair[0],key_pair[1]])
return
instantiate a key pair
make_keys()
def make_synthetic_previous_transaction(num_outputs):
"""
makes synthetic previous transaction with randomized values
we abstract away the inputs to the previous transaction.
"""
transaction = {}
transaction['version'] = 1
transaction['transactionid'] = rcrypt.make_uuid()
transaction['locktime'] = 0
transaction['vin'] = []
transaction['vout'] = []
def make_synthetic_vout():
"""
make a synthetic vout object.
returns a vout dictionary item
"""
vout = {}
vout['value'] = secrets.randbelow(1000000) + 1000 # helium cents
tmp = []
tmp.append('')
tmp.append('')
tmp.append(prev_tx_keys[0][1])
tmp.append('')
tmp.append('')
vout['ScriptPubKey'] = tmp
return vout
ctr = 0
while ctr < num_outputs:
ret = make_synthetic_vout()
transaction['vout'].append(ret)
ctr += 1
return transaction
#
Synthetic Transaction
#
def make_synthetic_transaction(prev_tx: "dictionary"):
"""
makes a synthetic transaction with randomized values and that
draws inputs from the previous transaction
"""
transaction = {}
transaction['version'] = 1
transaction['transactionid'] = rcrypt.make_uuid()
transaction['locktime'] = 0
transaction['vin'] = []
transaction['vout'] = []
num_outputs = max(1, secrets.randbelow(9))
ctr = 0
while ctr < num_outputs:
vout = make_synthetic_vout()
transaction['vout'].append(vout)
ctr += 1
randomize how many of the previous transaction outputs
are consumed and the order in which they are consumed
num_inputs = max(secrets.randbelow(len(prev_tx["vout"])), 1)
indices = list(range(num_inputs))
random.shuffle(indices)
for ctr in indices:
vin = make_synthetic_vin(prev_tx["transactionid"], ctr)
transaction['vin'].append(vin)
ctr += 1
return transaction
#
make a synthetic vin list
#
def make_synthetic_vin(prev_txid, prev_tx_vout_index):
"""
make a vin object, receives a previous transaction id
and an index into the vout list
returns the vin element
"""
vin = {}
vin['txid'] = prev_txid
vin['vout_index'] = prev_tx_vout_index
vin['ScriptSig'] = []
vin['ScriptSig'].append(rcrypt.sign_message(prev_tx_keys[0][0], prev_
tx_keys[0][1]))
vin['ScriptSig'].append(prev_tx_keys[0][1])
return vin
#
make a synthetic vout list
#
def make_synthetic_vout():
"""
make a randomized vout element
receives a public key
returns the vout dict element
"""
vout = {}
vout['value'] = 1 # helium cents
tmp = []
tmp.append('')
tmp.append('')
tmp.append(secrets.token_hex(64))
tmp.append('')
tmp.append('')
vout['ScriptPubKey'] = tmp
return vout
def make_transactionid():
"""
makes a random transaction id
"""
return rcrypt.make_uuid()
def make_vout_index():
"""
makes a random vout index
"""
return random.randrange(10)
def make_pkhash():
"""
creates a random pkhash value
"""
id = rcrypt.make_uuid()
return rcrypt.make_RIPEMD160_hash(rcrypt.make_SHA256_hash(id))
def make_value():
"""
creates a random value
"""
return random.randrange(1000000)
def make_spent():
"""
creates a random spent boolean
"""
sbool = random.randrange(10) % 2
if sbool: return False
return True
def test_put_keyvalue():
"""
put a key-value pair into the chainstate database
values have already been validated by validate_transaction
"""
ctr = 0
while ctr < 10:
txid = make_transactionid()
vout_index = make_vout_index()
keyvalue = {
"pkhash": make_pkhash(),
"value": make_value(),
"spent": make_spent(),
"tx_chain": ""
}
assert chain.put_transaction(txid + "" + str(vout_index), keyvalue) == True ctr += 1 def test_get_keyvalue(): """ get a key value for an existing key """ txid = make_transactionid() vout_index = make_vout_index() keyvalue = { "pkhash": make_pkhash(), "value": make_value(), "spent": make_spent(), "tx_chain": "" } assert chain.put_transaction(txid + "" + str(vout_index), keyvalue) == True
key = txid + "_" + str(vout_index)
ret = chain.get_transaction(key)
assert ret != False
assert type(ret) == dict
def test_get_for_fake_key():
"""
attempt to get a key value for a fictitious key
"""
txid = make_transactionid()
vout_index = make_vout_index()
keyvalue = {
"pkhash": make_pkhash(),
"value": make_value(),
"spent": make_spent(),
"tx_chain": ""
}
assert chain.put_transaction(txid + "" + str(vout_index), keyvalue) == True key = "junk" + str(vout_index)
ret = chain.get_transaction(key)
assert ret == False
def test_transaction_update():
"""
updates the HeliumDB to reflect a transaction
"""
make a synthetic previous transaction
num_prev_tx_outputs = secrets.randbelow(5) + 1
previous_tx = make_synthetic_previous_transaction(num_prev_tx_outputs)
reflect this previous transaction in HeliumDB
ctr = 0
for vout in previous_tx["vout"]:
pdb.set_trace()
fragment = {}
fragment["pkhash"] = vout["ScriptPubKey"][2]
fragment["spent"] = False
fragment["value"] = max(secrets.randbelow(1000000), 10)
fragment["tx_chain"] = ""
txid = previous_tx["transactionid"] + "_" + str(ctr)
chain.put_transaction( txid, fragment)
ctr += 1
make a transaction consuming some previous transaction outputs
trx = make_synthetic_transaction(previous_tx)
update the Helium Chainstate
assert chain.transaction_update(trx) == True
def test_double_spend():
"""
tests transaction update when a previous transaction fragment
has been spent
"""
make a synthetic previous transaction
num_prev_tx_outputs = secrets.randbelow(5) + 1
previous_tx = make_synthetic_previous_transaction(num_prev_tx_outputs)
reflect this previous transaction in HeliumDB
ctr = 0
for vout in previous_tx["vout"]:
pdb.set_trace()
fragment = {}
fragment["pkhash"] = vout["ScriptPubKey"][2]
fragment["spent"] = True
fragment["value"] = max(secrets.randbelow(1000000), 10)
fragment["tx_chain"] = ""
txid = previous_tx["transactionid"] + "_" + str(ctr)
chain.put_transaction( txid, fragment)
ctr += 1
make a transaction consuming some previous transaction outputs
trx = make_synthetic_transaction(previous_tx)
update the Helium Chainstate
assert chain.transaction_update(trx) == 0
def test_previous_tx_non_existent():
"""
tests transaction update when a previous transaction does
not exist
"""
make a synthetic previous transaction
num_prev_tx_outputs = secrets.randbelow(5) + 1
previous_tx = make_synthetic_previous_transaction(num_prev_tx_outputs)
reflect this previous transaction in HeliumDB
ctr = 0
for vout in previous_tx["vout"]:
pdb.set_trace()
fragment = {}
fragment["pkhash"] = vout["ScriptPubKey"][2]
fragment["spent"] = False
fragment["value"] = max(secrets.randbelow(1000000), 10)
fragment["tx_chain"] = ""
txid = previous_tx["transactionid"] + "_" + str(ctr)
chain.put_transaction( txid, fragment)
ctr += 1
make a transaction consuming some previous transaction outputs
trx = make_synthetic_transaction(previous_tx)
trx["vin"][0]["txid"] = "bad previous tx id"
update the Helium Chainstate
assert chain.transaction_update(trx) == False

actualizando el modulo TX

Ahora que tenemos un módulo Chainstate funcional, podemos actualizar el módulo tx para obtener utxos de transacciones anteriores. Asegúrese de importar los módulos hchaindb y json en tx. En el módulo tx, elimine la función prevtx_value e inserte la siguiente función en su lugar:

def prevtx_value(txkey: "string") -> 'bool':
"""
examines the chainstate database and returns the transaction fragment
corresponding to txkey:
{
"pkkhash": ,
"value": ,
"spent": ,
"tx_chain": <string>
}
receives a transaction fragment key:
"previous txid" + "_" + str(prevtx_vout_index)
Returns False if the transaction if does not exist, or if the
value has been spent or if value is not a positive integer
otherwise returns the previous transaction fragment data
"""
try:
fragment = hchaindb.get_transaction(txkey)
if fragment == False:
raise(ValueError("cannot get fragment from chainstate"))
return False if the value has been spent or if the value is
not positive
if fragment["spent"] == True:
raise(ValueError("cannot respend fragment in chainstate: " +
txkey))
if fragment["value"] <= 0:
raise(ValueError("fragment value <= 0"))
except Exception as err:
logging.debug('prevtx_value: exception: ' + str(err))
return False
return fragment

prevtx_value devuelve False si no puede obtener un utxo de la transacción del Chainstate. Chainstate devolverá False si la clave de transacción es ficticia. prevtx_value
también devuelve False si el elemento de valor en el diccionario devuelto no es un entero positivo o si el valor se ha gastado.

la base de datos blk_index

La base de datos blk_index nos permite determinar el bloque que contiene una determinada transacción. Esta es una base de datos de LevelDB. La base de datos blk_index tiene cinco funciones de interfaz:


open_blk_index (ruta_directorio: “cadena”)
close_blk_index ()
get_blockno (clave: “cadena”) => “bool o entero”
put_index (clave: “cadena”, valor: “entero”) => “bool”
delete_index (clave: “cadena”) => “bool”


El open_blk_index abre una base de datos Blancoin LevelDB o la crea si no existe. Esta función recibe un parámetro de ruta de archivo completo o parcial,
incluido el nombre de la base de datos. close_blk_index cierra la base de datos.
La función get_blockno tiene un argumento de clave de transacción. Esta función devuelve el número de bloque (o altura del bloque) que contiene esta transacción o False si la clave de la transacción no existe en blk_index. Ten en cuenta que este es un ID de transacción, no un puntero a un
utxo de transacción. La función put_index agrega un par clave-valor a blk_index. Esta función recibe una clave de identificación de transacción y un número de bloque (o altura de bloque) como argumentos. Devuelve True si el par clave-valor se agrega a blk_index y False en caso contrario. Ten en cuenta que el bloque de génesis tiene altura cero.
delete_index recibe una clave de transacción y elimina la clave de transacción. Esta función devuelve False si la clave no existe o si hay algún otro error. De lo contrario, la función devuelve True.

el codigo de blk_index

Crearemos una base de datos de Blancoin blk_index vacía. Esta base de datos estará ubicada en el subdirectorio de datos de la raíz de la aplicación Blancoin. Navega a la raíz de tu entorno virtual, ingrese al shell de Python y ejecute:

create the Blancoin blk_index key-value store
>>> db = plyvel.DB('data/blk_index')

Si atraviesa el directorio de datos, verá que un subdirectorio blk_index ha sido creado. Este subdirectorio contiene la base de datos Blancoin blk_index.
Copie el siguiente código de programa en el archivo blk_index.py y copie este archivo en el
directorio de chainstate:

"""
Implementation of the Helium block index key-value store
lets us search for the block that contains a transaction
"""
import plyvel
import pdb
import logging
import json
import rcrypt
"""
log debugging messages to the file debug.log
"""
logging.basicConfig(filename="debug.log",filemode="w", format='%(asctime)
s:%(levelname)s:%(message)s',
level=logging.DEBUG)
handle to the Helium blk_index key-value store
bDB = None
def open_blk_index(filepath: "string") -> "db handle or False":
"""
opens the Helium blk_index store and returns a handle to
the database. Any directories in filepath must exist. The
database will be created if it does not exist.
Returns a handle to the database or False
"""
try:
global bDB
bDB = plyvel.DB(filepath, create_if_missing=True)
print("blk_index opened")
except Exception as err:
print("open_blk_index: exception: " + str(err))
logging.debug("open_blk_index: exception: " + str(err))
return False
return bDB
def close_blk_index() -> "bool":
"""
close the Helium block index store
Returns True if the database is closed, False otherwise
"""
try:
bDB.close()
if bDB.closed != True: return False
except Exception as err:
logging.debug("close_blk_index: exception: " + str(err))
return False
return True
def get_blockno(txid: "string") -> "False or integer":
"""
Receives a transaction id (not a transaction fragment id)
returns the block no. of the block that contains the transaction:
"""
try:
get the block no, return False if the
transaction does not exist in the store
block_no = bDB.get(str.encode(txid))
if block_no == None:
raise(ValueError("txid does not have a block no."))
block_no = block_no.decode()
block_no = int(block_no)
except Exception as err:
logging.debug('get_blockno: exception: ' + str(err))
return False
return block_no
def put_index(txid: "string", blockno: "integer") -> "bool":
"""
Returns True if the trainsactionid_block_no key-value pair is created
and False otherwise
"""
try:
if len(txid.strip()) != 64:
raise(ValueError("txid invalid length"))
if len(str(blockno).strip()) == 0:
raise(ValueError("blockno field is empty"))
if blockno < 0:
raise(ValueError("negative blockno"))
save the transactionid-block no pair in the store
bDB.put(str.encode(txid), str.encode(str(blockno)))
except Exception as err:
logging.debug('put_blockno: exception: ' + str(err))
return False
return True
def delete_index(txid: "string"):
"""
deletes a (txid, blockno) pair from the blk_index store
"""
try:
bDB.delete(str.encode(txid))
except Exception as err:
logging.debug('delete_tx_blockno: exception: ' + str(err))
return False
return True

pytests para la base de datos blk_index

Copia el siguiente código de prueba unitaria en el archivo test_blk_index.py y guarda este archivo en el directorio unit_tests. Diríjase al directorio unit_tests y ejecute las pruebas en el entorno de Blancoin:

> (virtual): pytest test_blk_index.py -s

Debes observar que pasan nueve pruebas:

"""
pytest unit tests for the blk_index module
"""
import blk_index as blkindex
import rcrypt
import pytest
import json
import random
import secrets
import pdb
import os
def setup_module():
assert bool(blkindex.open_blk_index("hblk_index")) == True
if os.path.isfile("hblk_index"):
os.remove("hblk_index")
def teardown_module():
assert blkindex.close_blk_index() == True
@pytest.mark.parametrize("txid, value, ret", [
("", 9, False),
("abc", "", False),
('290cfd3f3e5027420ed483871309d2a0780b6c3092bef3f26347166b586ff89f',
10899, True),
(rcrypt.make_uuid(), 9, True),
(rcrypt.make_uuid(), -1, False),
(rcrypt.make_uuid(), -56019, False),
(rcrypt.make_uuid(), 90890231, True),
])
def test_put_index(txid, value, ret):
"""
tests putting valid and invalid transaction_index and blockno pairs
into the blk_index store
"""
assert blkindex.put_index(txid, value) == ret
def test_get_existing_tx():
"""
test getting a block for an existing transaction
"""
txid = rcrypt.make_uuid()
blkindex.put_index(txid, 555)
assert blkindex.get_blockno(txid) == 555
def get_faketx():
"""
test getting a block for a fake transaction
"""
assert blkindex.get_blockno("dummy tx") == False
def test_delete_tx():
txid = rcrypt.make_uuid()
blkindex.put_index(txid, 555)
assert blkindex.delete_index(txid) == True
assert blkindex.get_blockno(txid) == False

actualizando el modulo blockchain

Ahora que tenemos el código funcional para las bases de datos Chainstate y blk_index,
puede usar estas bases de datos para indexar la cadena de bloques. El algoritmo blk_index es particularmente sencillo. Cada vez que se agrega un bloque a la cadena de bloques, haz:

for transaction in block:
add blk_index[transaction id] = block["height"]

Elimine la función add_block en el módulo hblockchain y agregue lo siguiente
código de función en su lugar:

def add_block(block: "dictionary") -> "bool":
"""
add_block: adds a block to the blockchain. Receives a block.
The block attributes are checked for validity and each transaction in
the block is
tested for validity. If there are no errors, the block is written to a
file as a
sequence of raw bytes. Then the block is added to the blockchain.
The chainstate database and the blk_index databases are updated.
returns True if the block is added to the blockchain and False otherwise
"""
try:
validate the received block parameters
if validate_block(block) == False:
raise(ValueError("block validation error"))
validate the transactions in the block
update the chainstate database
for trx in block['tx']:
first transaction in the block is a coinbase transaction
if block["height"] == 0 or block['tx'][0] == trx:
zero_inputs = True
else: zero_inputs = False
if tx.validate_transaction(trx, zero_inputs) == False:
raise(ValueError("transaction validation error"))
if hchaindb.transaction_update(trx) == False:
raise(ValueError("chainstate update transaction error"))
serialize the block to a file
if (serialize_block(block) == False):
raise(ValueError("serialize block error"))
add the block to the blockchain in memory
blockchain.append(block)
update the blk_index
for transaction in block['tx']:
blockindex.put_index(transaction["transactionid"], block["height"])
except Exception as err:
print(str(err))
logging.debug('add_block: exception: ' + str(err))
return False
return True

Asegúrate de importar el módulo blk_index:

import blk_index as blockindex

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s