Las pruebas unitarias nos brindan garantías de la corrección del código. En consecuencia, deberíamos siempre esforzarse por proporcionar una gran cantidad de cobertura a nuestro código con pruebas unitarias.
Conceptualmente, una prueba unitaria una pequeña pieza discreta de código. Este fragmento de código es normalmente una función, pero también puede ser algo pequeño como una expresión o una colección de expresiones. Usaremos el módulo Python Pytest para realizar pruebas unitarias en nuestro modulo.
Instala el módulo Pytest desde la raíz de su entorno virtual Blancoin:
>(virtual) pip install pytest
Cree un archivo llamado test_rcrypt.py en el directorio unit_tests de su entorno virtual y luego copie el siguiente código fuente en este archivo:
import pytest
import rcrypt
import pdb
@pytest.mark.parametrize("input_string, value", [
('hello world', True),
('the quick brown fox jumped over the sleeping dog', True),
('0', True),
('', True)
])
def test_make_SHA256_hash(input_string, value):
"""
test that a SHA-256 message digest is created
"""
ret = rcrypt.make_SHA256_hash(input_string)
assert rcrypt.validate_SHA256_hash(ret) == value
@pytest.mark.parametrize("input_string, value", [
('a silent night and a pale paper moon over the saskatchewan river', 64),
('Farmer Brown and the sheep-dog', 64),
('', 64)
])
def test_SHA256_hash_length(input_string, value):
"""
test that the length of created SHA3-256 hash in hexadecimal format is
is 64 bytes
"""
assert len(rcrypt.make_SHA256_hash('hello world')) == 64
@pytest.mark.parametrize("digest, value", [
("123", False),
("644bcc7e564373040999aac89e7622f3ca71fba1d972fd94a31c3bfbf24e3938", True),
("644bcc7e564373040999aac89e7622f3ca71fba1d972fd94a31c3bfbf24e39380", False),
("644bcc7e564373040999aac89e7622f3ca71fba1d972fd94a31cZbfbf24e3938", False),
('', False),
("644bcc7e564373040999aac89e7622f3caz1fba1d972fd94a31c3bfbf24e3938", False),
])
def test_validate_SHA256_hash(digest, value):
"""
validate whether a valid SHA-256 message digest format is generated
"""
assert rcrypt.validate_SHA256_hash(digest) == value
@pytest.mark.parametrize("string_input, value", [
("0", True),
('andromeda galaxy', True),
('Farmer Brown and the sheep-dog', True),
('0', True)
])
def test_make_RIPEMD160_hash(string_input, value):
"""
validate that a valid RIPEMD-160 message digest format is generated
"""
ret = rcrypt.make_RIPEMD160_hash(string_input)
assert rcrypt.validate_RIPEMD160_hash(ret) == value
@pytest.mark.parametrize("hash, value", [
('123456789098765432169Q156c79FFa1200CCB1A', False),
("lkorkflfor4flgofmr", False),
('off0099ijf87', False),
('1234567890A87654321690156c79FFa1200CCB1A', True),
])
def test_validate_RIPEMD160_hash(hash, value):
"""
validate whether a string is in RIPEMD-160 message digest format
"""
assert rcrypt.validate_RIPEMD160_hash(hash) == value
def test_make_ecc_keys():
"""
test ECC private-public key pair
"""
ecc_keys = rcrypt.make_ecc_keys()
assert ecc_keys[0].find("BEGIN PRIVATE KEY") >= 0
assert ecc_keys[0].find("END PRIVATE KEY") >= 0
assert ecc_keys[0].find("END PRIVATE KEY") > ecc_keys[0].find("BEGIN
PRIVATE KEY")
assert ecc_keys[1].find("BEGIN PUBLIC KEY") >= 0
assert ecc_keys[1].find("END PUBLIC KEY") >= 0
assert ecc_keys[1].find("END PUBLIC KEY") > ecc_keys[1].find("BEGIN
PUBLIC KEY")
@pytest.mark.parametrize("message, value", [
('50bfee49c706d766411777aac1c9f35456c33ecea2afb4f3c8f1033b0298bdc9', True),
('6e6404d8693aea119a8ef7cf82c56fe96555d8df2c36c2b9e325411fbef62014', True),
('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', True),
('hello world', True),
])
def test_sign_message(message, value):
"""
Digitally sign a message and then verify it
"""
ecc_tuple = rcrypt.make_ecc_keys()
priv_key = ecc_tuple[0]
pub_key = ecc_tuple[1]
sig = rcrypt.sign_message(priv_key, message)
ret = rcrypt.verify_signature(pub_key, message, sig)
assert ret == value
@pytest.mark.parametrize("prefix, value", [
("a", False),
("1", True),
("Q", False),
("qwerty", False),
("", False),
])
def test_make_address(prefix, value):
"""
test the generation of Helium addresses from seeds
"""
ret = rcrypt.make_address(prefix)
assert rcrypt.validate_address(ret) == value
@pytest.mark.parametrize("length, ret", [
(64, True),
(32, False)
])
def test_make_uuid(length, ret):
"""
test id generation
"""
id = rcrypt.make_uuid()
assert (len(id) == length) == ret
El Python Logger
Advierte que hacemos un uso extensivo del registrador de Python para ayudarnos a depurar el código fuente. El siguiente ejemplo demuestra su uso:
import logging
logging.basicConfig(filename="debug.log", format='%(asctime)s:%(levelname)
s:%(message)s',
level=logging.DEBUG)
...
def adder(x,y):
z = x + y
logging.debug("This is a debug message")
logging.info("Informational message, output = " + str(z))
if z != x + y:
logging.error("An error has happened!")
return z
Los parámetros de la función logging.basicConfig indican que toda la salida de registro debe escribirse en el archivo debug.log y que todos los mensajes de registro tengan un nivel de gravedad de registro DEBUG o superior. Los niveles de registro en orden de gravedad creciente son:
DEBUG, INFO, WARNING, ERROR, CRITICAL
El segundo parámetro del logging es opcional; especifica el formato del
salida del registrador.
Una vez que hemos establecido la configuración del logging, podemos especificar un mensaje de registro para este loggingde la siguiente manera:
logger.SEVERITY_LEVEL("Some String %s: %s", message_1, message_2)
SEVERITY_LEVEL is a log level: DEBUG, INFO ... CRITICAL.
Esto, por ejemplo, generará un resultado como este:
2020-02-01 16:05:03,969:SEVERITY_LEVEL:validate_block: nonce is negative
De forma predeterminada, el logging agrega mensajes a un archivo de registro. Podemos instruir al logging para que sobrescriba un archivo de log existente especificando un parámetro de modo de archivo opcional:
logging.basicConfig(filename="debug.log", filemode='w', level=DEBUG)
estructura del bloque
En esta sección, vamos a ver la estructura de un bloque de blancoin. De un post anterior,
recordará que una blockchain es una colección ordenada e inmutable de bloques que tienen validación criptográfica. Cada bloque de blancoin es un contenedor de transacciones.
Un bloque es la siguiente estructura de datos del diccionario:
{
"prevblockhash": <string>
"version": <string>
"timestamp": <integer>
"difficulty_bits": <integer>
"nonce": <integer>
"merkle_root": <string>
"height": <integer>
"tx": <list>
}
En Python, un entero es un entero con signo. Python3 elimina la diferencia entre
enteros y enteros largos. Los enteros pueden tener cualquier tamaño y solo están limitados por la memoria.
Python puede realizar operaciones aritméticas en números enteros de tamaño ilimitado.
prevblockhash es el hash del mensaje SHA-256 del encabezado del bloque anterior en formato hexadecimal. Este valor se usa para probar si el bloque anterior ha sido
manipulado. El valor prevblockhash del bloque génesis es la cadena vacía.
El subconjunto de atributos de bloque prevblockhash, version, timestamp, difficulty_bits,
nonce y merkle_root se denomina block header. El header no incluye la lista de transacciones. Debido a que el merkle root está incluido en el encabezado, podemos probar la validez de todo el bloque, incluidas las transacciones, calculando el hash SHA-256 del mensaje del encabezado solamente.
version es el número de versión de Blancoin que pertenece este bloque. Este atributo es
establecido en el módulo de configuración de blancoin y es «1».
timestamp es la marca de tiempo de Unix (time epoch) de cuando se creó el bloque.
El time epoch es la cantidad de segundos que han transcurrido desde la medianoche del 1 de enero de 1970 en la hora del meridiano de Greenwich o UTC.
dificultad_bits es DIFICULTAD_BITS como se define en la configuración de blancoin. Este es el valor cuando se extrajo el bloque. Tenga en cuenta que el parametro DIFFICULTY_BITS se varía periódicamente a medida que el tiempo promedio para extraer un bloque se ajusta a diez minutos.
nonce es un número utilizado en el algoritmo de minería de blancoin.
merkle_root es una cadena hexadecimal SHA-256. La raíz de merkle nos permite asegurarnos de que las transacciones en el bloque no han sido manipuladas. También nos permite determinar si una transacción particular está presente en el bloque. Hablaremos de este valor en un
post futuro.
heigth es la altura de este bloque. Los bloques en una cadena de bloques se ordenan de conformidad con el algoritmo de consenso distribuido y se puede visualizar como una pila, uno en encima de otro. El primer bloque, que es el bloque de génesis, tiene una altura de 0.
tx es una lista de transacciones. El número de transacciones en la lista está limitado por el
tamaño máximo permitido de un bloque. En el módulo de configuración de blancoin, este valor esta establecido en 1 MB.
Ahora que hemos definido un bloque, podemos especificar una cadena de bloques de blancoin:
blockchain = []
La cadena de bloques es una lista y cada elemento de la lista es un bloque.
El core de blancoin
Ahora estamos listos para ver el código de Blancoin. Copie el siguiente código en un archivo llamado hblockchain.py y copie este archivo en el directorio block
Este no es el código completo para el módulo blockchain ya que las transacciones y las operaciones de la base de datos han sido abstraidas. Dado que no hemos desarrollado el código para los módulos de transacciones y bases de datos hasta el momento, simulamos operaciones relativas a estos módulos con valores mágicos.
El código de la cadena de bloques Blancoin usa los módulos json y pickle. Ambos módulos son parte de la biblioteca estándar de Python y, por lo tanto, no los necesita instalar. JSON es un estándar de intercambio de datos que puede codificar objetos Python en cadenas
y luego decodificar estas cadenas de nuevo en los objetos correspondientes. Pickle serializa objetos convirtiendo objetos en una secuencia de bytes y luego escribiendo esta secuencia a un archivo. Este proceso se llama serialización. Pickle puede deserializar archivos para recuperar los objetos originales. Si nunca ha usado Pickle antes, consulta en Internet.
La función add_block agrega un bloque a la cadena de bloques Blancoin. Esta función
recibe un bloque. La función comprueba si los atributos del bloque tienen valores válidos. En particular, verifica que las transacciones incluidas en el bloque sean válidas. En el caso
que el bloque no es válido, la función regresa con un valor false. Si el bloque es válido, la función serializa el bloque en un archivo de bytes sin formato a través del módulo Pickle. Este bloque luego se agrega a la lista de blockchain en la memoria.
La función serialize_block serializa un bloque en el directorio de datos usando Pickle. El nombre del archivo se construye de la siguiente manera:
filename = "block_" + block_height + ".dat"
La función read_block recibe un índice en la cadena de bloques y devuelve el bloque
correspondiente al índice. Se lanzará una excepción si el índice está fuera de los límites.
La función blockheader_hash recibe un bloque y devuelve el hash SHA-256 del header como una cadena hexadecimal.
La función validate_block prueba si un bloque tiene valores válidos. Esta función también valida el bloque anterior comparando hashes SHA-256.
Aqui está el código de hblockchain.py
"""
hbockchain.py: This module creates and maintains the Helium blockchain
This is a partial implementation of the module
"""
import rcrypt
import hconfig
import json
import pickle
import pdb
import logging
import os
"""
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)
"""
A block is a Python dictionary that has the following
structure. The type of an attribute is denoted in angle delimiters.
{
"prevblockhash": <string>
"version": <string>
"timestamp": <integer>
"difficulty_bits": <integer>
"nonce": <integer>
"merkle_root": <string>
"height": <integer>
"tx": <list>
}
The blockchain is a list where each list element is a block
This is also referred to as the primary blockchain when used
by miners.
"""
blockchain = []
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.
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"))
# 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)
except Exception as err:
print(str(err))
logging.debug('add_block: exception: ' + str(err))
return False
return True
def serialize_block(block: "dictionary") -> "bool":
"""
serialize_block: serializes a block to a file using pickle.
Returns True if the block is serialized and False otherwise.
"""
index = len(blockchain)
filename = "block_" + str(index) + ".dat"
# create the block file and serialize the block
try:
f = open(filename, 'wb')
pickle.dump(block, f)
except Exception as error:
logging.debug("Exception: %s: %s", "serialize_block", error)
f.close()
return False
f.close()
return True
def read_block(blockno: 'long') -> "dictionary or False":
"""
read_block: receives an index into the Helium blockchain.
Returns a block or False if the block does not exist.
"""
try:
block = blockchain[blockno]
return block
except Exception as error:
logging.debug("Exception: %s: %s", "read_block", error)
return False
return block
def blockheader_hash(block: 'dictionary') -> "False or String":
"""
blockheader_hash: computes and returns SHA-256 message digest of a
block header
as a hexadecimal string.
Receives a block those blockheader hash is to be computed.
Returns False if there is an error, otherwise returns a SHA-256
hexadecimal string.
The block header consists of the following block fields:
(1) version, (2)previous block hash, (3) merkle root
(4) timestamp, (5) difficulty_bits, and (6) nonce.
"""
try:
hash = rcrypt.make_SHA256_hash(block['version'] +
block['prevblockhash'] +
block['merkle_root'] +
str(block['timestamp']) +
str(block['difficulty_bits']) +
str(block['nonce']))
except Exception as error:
logging.debug("Exception:%s: %s", "blockheader_hash", error)
return False
return hash
def validate_block(block: "dictionary") -> "bool":
"""
validate_block: receives a block and verifies that all its attributes have
valid values.
Returns True if the block is valid and False otherwise.
"""
try:
if type(block) != dict:
raise(ValueError("block type error"))
# validate scalar block attributes
if type(block["version"]) != str:
raise(ValueError("block version type error"))
if block["version"] != hconfig.conf["VERSION_NO"]:
raise(ValueError("block wrong version"))
Chapter 8 The Helium Blockchain
99
if type(block["timestamp"]) != int:
raise(ValueError("block timestamp type error"))
if block["timestamp"] < 0:
raise(ValueError("block invalid timestamp"))
if type(block["difficulty_bits"]) != int:
raise(ValueError("block difficulty_bits type error"))
if block["difficulty_bits"] <= 0:
raise(ValueError("block difficulty_bits <= 0"))
if type(block["nonce"]) != int:
raise(ValueError("block nonce type error"))
if block["nonce"] != hconfig.conf["NONCE"]:
raise(ValueError("block nonce is invalid"))
if type(block["height"]) != int:
raise(ValueError("block height type error"))
if block["height"] < 0:
raise(ValueError("block height < 0"))
if len(blockchain) == 0 and block["height"] != 0:
raise(ValueError("genesis block invalid height"))
if len(blockchain) > 0:
if block["height"] != blockchain[-1]["height"] + 1:
raise(ValueError("block height is not in order"))
# The length of the block must be less than the maximum block size that
# specified in the config module.
# json.dumps converts the block into a json format string.
if len(json.dumps(block)) > hconfig.conf["MAX_BLOCK_SIZE"]:
raise(ValueError("block length error"))
# validate the merkle_root.
if block["merkle_root"] != merkle_root(block["tx"], True):
raise(ValueError("merkle roots do not match"))
# validate the previous block by comparing message digests.
# the genesis block does not have a predecessor block
if block["height"] > 0:
if block["prevblockhash"] != blockheader_hash(blockchain[block[
"height"]-1]):
raise(ValueError("previous block header hash does not match"))
else:
if block["prevblockhash"] != "":
raise(ValueError("genesis block has prevblockhash"))
# genesis block does not have any input transactions
if block["height"] == 0 and block["tx"][0]["vin"] !=[]:
raise(ValueError("missing coinbase transaction"))
# a block other than the genesis block must have at least
# two transactions: the coinbase transaction and at least
# one more transaction
if block["height"] > 0 and len(block["tx"]) < 2:
raise(ValueError("block only has one transaction"))
except Exception as error:
logging.error("exception: %s: %s", "validate_block",error)
return False
return True
def merkle_root(buffer: "List", start: "bool" = False) -> "bool or string":
"""
merkle_tree: computes the merkle root for a list of transactions.
Receives a list of transactions and a boolean flag to indicate whether
the function has been called for the first time or whether it is a
recursive call from within the function.
Returns the root of the merkle tree or False if there is an error.
"""
pass
pruebas unitarias Blancoin
Copie el siguiente código en un archivo llamado test_blockchain.py y guárdelo en el directorio unit_tests. Ahora puede ir a este directorio y ejecutar todas las pruebas:
>(virtual) pytest test_blockchain.py -v -s
Debería pasar 23 pruebas unitarias.
"""
test blockchain functionality
transaction values are synthetic.
"""
import pytest
import hblockchain
import hconfig
import rcrypt
import time
import os
import pdb
import secrets
def teardown_module():
"""
after all of the tests have been executed, remove any blocks that were
created
"""
os.system("rm *.dat")
hblockchain.blockchain.clear()
###################################################
# Make A Synthetic Random Transaction For Testing
###################################################
def make_random_transaction(block_height):
tx = {}
tx["version"] = "1"
tx["transactionid"] = rcrypt.make_uuid()
tx["locktime"] = secrets.randbelow(hconfig.conf["MAX_LOCKTIME"])
# public-private key pair for previous transaction
prev_keys = rcrypt.make_ecc_keys()
# public-private key pair for this transaction
keys = rcrypt.make_ecc_keys()
# Build vin
tx["vin"] = []
if block_height > 0:
ctr = secrets.randbelow(hconfig.conf["MAX_INPUTS"]) + 1
ind = 0
while ind < ctr:
signed = rcrypt.sign_message(prev_keys[0], prev_keys[1])
ScriptSig = []
ScriptSig.append(signed[0])
ScriptSig.append(prev_keys[1])
tx["vin"].append({
"txid": rcrypt.make_uuid(),
"vout_index": ctr,
"ScriptSig": ScriptSig
})
ind += 1
# Build Vout
tx["vout"] = []
ctr = secrets.randbelow(hconfig.conf["MAX_OUTPUTS"]) + 1
ind = 0
while ind < ctr:
ScriptPubKey = []
ScriptPubKey.append("DUP")
ScriptPubKey.append("HASH-160")
ScriptPubKey.append(keys[1])
ScriptPubKey.append("EQ_VERIFY")
ScriptPubKey.append("CHECK-SIG")
tx["vout"] = {
"value": secrets.randbelow(10000000) + 1000000,
# helium cents
"ScriptPubKey": ScriptPubKey
}
ind += 1
return tx
#############################################
# Build Three Synthetic Blocks For Testing
#############################################
block_0 = {
"prevblockhash": "",
"version": "1",
"timestamp": 0,
"difficulty_bits": 20,
"nonce": 0,
"merkle_root": rcrypt.make_SHA256_hash('msg0'),
"height": 0,
"tx": [make_random_transaction(0)]
}
block_1 = {
"prevblockhash": hblockchain.blockheader_hash(block_0),
"version": "1",
"timestamp": 0,
"difficulty_bits": 20,
"nonce": 0,
"merkle_root": rcrypt.make_SHA256_hash('msg1'),
"height": 1,
}
block_1["tx"] = []
block_1["tx"].append(make_random_transaction(1))
block_1["tx"].append(make_random_transaction(1))
block_2 = {
"prevblockhash": hblockchain.blockheader_hash(block_1),
"version": "1",
"timestamp": 0,
"difficulty_bits": 20,
"nonce": 0,
"merkle_root": rcrypt.make_SHA256_hash('msg2'),
"height": 2,
}
block_2["tx"] = []
block_2["tx"].append(make_random_transaction(2))
block_2["tx"].append(make_random_transaction(2))
def test_block_type(monkeypatch):
"""
tests the type of a block
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
assert hblockchain.validate_block(block_0) == True
def test_add_good_block(monkeypatch):
"""
test add a good block
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
assert hblockchain.add_block(block_0) == True
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
assert hblockchain.add_block(block_1) == True
hblockchain.blockchain.clear()
def test_missing_version(monkeypatch):
"""
test for a missing version number
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
monkeypatch.setitem(block_1, "version", "")
assert hblockchain.add_block(block_1) == False
def test_version_bad(monkeypatch):
"""
test for an unknown version number
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
monkeypatch.setitem(block_1, "version", -1)
assert hblockchain.add_block(block_1) == False
def test_bad_timestamp_type(monkeypatch):
"""
test for a bad timestamp type
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
monkeypatch.setitem(block_1, "timestamp", "12345")
assert hblockchain.add_block(block_1) == False
def test_negative_timestamp(monkeypatch):
"""
test for a negative timestamp
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
monkeypatch.setitem(block_0, "timestamp", -2)
assert hblockchain.add_block(block_0) == False
def test_missing_timestamp(monkeypatch):
"""
test for a missing timestamp
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
monkeypatch.setitem(block_1, "timestamp", "")
assert hblockchain.add_block(block_1) == False
def test_block_height_type(monkeypatch):
"""
test the type of the block height parameter
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
monkeypatch.setitem(block_0, "height", "0")
hblockchain.blockchain.clear()
assert hblockchain.add_block(block_0) == False
def test_bad_nonce(monkeypatch):
"""
test for a negative nonce
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
monkeypatch.setitem(block_1, "nonce", -1)
assert hblockchain.add_block(block_1) == False
def test_missing_nonce(monkeypatch):
"""
test for a missing nonce
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
monkeypatch.setitem(block_0, "nonce", "")
assert hblockchain.add_block(block_0) == False
def test_block_nonce_type(monkeypatch):
"""
test nonce has the wrong type"
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
monkeypatch.setitem(block_0, "nonce", "0")
assert hblockchain.add_block(block_0) == False
def test_negative_difficulty_bit(monkeypatch):
"""
test for negative difficulty bits
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
monkeypatch.setitem(block_1, "difficulty_bits", -5)
assert hblockchain.add_block(block_1) == False
def test_difficulty_type(monkeypatch):
"""
test difficulty bits has the wrong type"
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
monkeypatch.setitem(block_0, "difficulty_bits", "20")
assert hblockchain.add_block(block_0) == False
def test_missing_difficulty_bit(monkeypatch):
"""
test for missing difficulty bits
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('data'))
monkeypatch.setitem(block_1, "difficulty_bits", '')
assert hblockchain.add_block(block_1) == False
def test_read_genesis_block(monkeypatch):
"""
test reading the genesis block from the blockchain
"""
hblockchain.blockchain.clear()
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
hblockchain.add_block(block_0)
assert hblockchain.read_block(0) == block_0
hblockchain.blockchain.clear()
def test_genesis_block_height(monkeypatch):
"""
test genesis block height
"""
hblockchain.blockchain.clear()
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
block_0["height"] = 0
assert hblockchain.add_block(block_0) == True
blk = hblockchain.read_block(0)
assert blk != False
assert blk["height"] == 0
hblockchain.blockchain.clear()
def test_read_second_block(monkeypatch):
"""
test reading the second block from the blockchain
"""
hblockchain.blockchain.clear()
assert len(hblockchain.blockchain) == 0
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
monkeypatch.setitem(block_1, "prevblockhash", hblockchain.blockheader_
hash(block_0))
ret = hblockchain.add_block(block_0)
assert ret == True
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
ret = hblockchain.add_block(block_1)
assert ret == True
block = hblockchain.read_block(1)
assert block != False
hblockchain.blockchain.clear()
def test_block_height(monkeypatch):
"""
test height of the the second block
"""
hblockchain.blockchain.clear()
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
monkeypatch.setitem(block_0, "height", 0)
monkeypatch.setitem(block_0, "prevblockhash", "")
monkeypatch.setitem(block_1, "height", 1)
monkeypatch.setitem(block_1, "prevblockhash", hblockchain.blockheader_
hash(block_0))
assert hblockchain.add_block(block_0) == True
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
assert hblockchain.add_block(block_1) == True
blk = hblockchain.read_block(1)
assert blk != False
assert blk["height"] == 1
hblockchain.blockchain.clear()
def test_block_size(monkeypatch):
"""
The block size must be less than hconfig["MAX_BLOCKS"]
"""
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
arry = []
filler = "0" * 2000000
arry.append(filler)
monkeypatch.setitem(block_0, "tx", arry)
hblockchain.blockchain.clear()
assert hblockchain.add_block(block_0) == False
def test_genesis_block_prev_hash(monkeypatch):
"""
test that the previous block hash for the genesis block is empty
"""
hblockchain.blockchain.clear()
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
monkeypatch.setitem(block_0, "height", 0)
monkeypatch.setitem(block_0, "prevblockhash", rcrypt.make_uuid() )
assert len(hblockchain.blockchain) == 0
assert hblockchain.add_block(block_0) == False
def test_computes_previous_block_hash(monkeypatch):
"""
test previous block hash has correct format
"""
val = hblockchain.blockheader_hash(block_0)
rcrypt.validate_SHA256_hash(val) == True
def test_invalid_previous_hash(monkeypatch):
"""
test block's prevblockhash is invalid
"""
hblockchain.blockchain.clear()
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
monkeypatch.setitem(block_2, "prevblockhash", \
"188a1fd32a1f83af966b31ca781d71c40f756a3dc2a7ac44ce89734d2186f632")
hblockchain.blockchain.clear()
assert hblockchain.add_block(block_0) == True
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
assert hblockchain.add_block(block_1) == True
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg2'))
assert hblockchain.add_block(block_2) == False
hblockchain.blockchain.clear()
def test_no_consecutive_duplicate_blocks(monkeypatch):
"""
test cannot add the same block twice consecutively to the blockchain
"""
hblockchain.blockchain.clear()
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg0'))
assert hblockchain.add_block(block_0) == True
monkeypatch.setattr(hblockchain, "merkle_root", lambda x, y: \ rcrypt.
make_SHA256_hash('msg1'))
monkeypatch.setitem(block_1, "prevblockhash", hblockchain.blockheader_
hash(block_0))
assert hblockchain.add_block(block_1) == True
monkeypatch.setitem(block_1, "height", 2)
assert hblockchain.add_block(block_1) == False
hblockchain.blockchain.clear()
Para el próximo post escribiremos el código las transacciones de Blancoin.