diff src/block.py @ 4:e7a84094bf07

refactor code
author Dennis Concepcion Martin <dennisconcepcionmartin@gmail.com>
date Wed, 20 Oct 2021 19:36:39 +0200
parents 3d83609e12a1
children 1a8d94b500d8
line wrap: on
line diff
--- a/src/block.py	Sun Oct 17 17:33:26 2021 +0200
+++ b/src/block.py	Wed Oct 20 19:36:39 2021 +0200
@@ -1,99 +1,126 @@
 import hashlib
-from src.helpers import read_bytes
-from src.helpers import get_variable_int
+from src.block_structure import *
 
 
-class Block:
-    """
-    Block structure
-    """
-
-    block_hash = str()
-    magic_number = int()
-    size = int()
-    number_of_transactions = int()
-    transactions = []
-
-    class Header:
-        version = int()
-        previous_block_hash = str()
-        merkle_root = str()
-        timestamp = int()  # Epoch Unix time
-        difficult_target = int()  # Bits
-        nonce = int()
-
-
-class Transaction:
-    id = str()
-    version = int()
-    number_of_inputs = int()
-    inputs = []
-    number_of_outputs = int()
-    outputs = []
-
-    class TransactionInput:
-        id = str()
-        is_coinbase = False
-        vout = int()
-        script_sig_size = int()
-        script_sig = str()
-        sequence = int()
-
-    class TransactionOutput:
-        value = float()
-        script_pub_key_size = int()
-        script_pub_key = str()
-
-
-def read_block(file):
+def read_block(f):
     """
     Deserialize block
     More info about block structure: https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch09.asciidoc
-    :param file: <class '_io.BufferedReader'>, required
+    More info about bytes order: https://en.wikipedia.org/wiki/Endianness
+    :param f: buffer, required
     :return:
     """
 
     block = Block()
-    block.magic_number = int.from_bytes(read_bytes(file, 4), 'big')
-    block.size = int.from_bytes(read_bytes(file, 4), 'big')
+    _ = f.read(4)  # Magic number
+    _ = f.read(4)[::-1]  # Block size
+    header_bytes = f.read(80)
+    block.h = __get_hash(header_bytes)
+    f.seek(8)
+    block.header = __get_header(f)
+    number_of_transactions = __get_variable_int(f)
+    for transaction_number in range(number_of_transactions):
+        block.transactions.append(__get_transaction(f))
 
-    # Compute block hash
-    header_bytes = read_bytes(file, 80, 'forward')
-    block_hash = hashlib.sha256(header_bytes).digest()
-    block_hash = hashlib.sha256(block_hash).digest()
+    return block.__dict__
+
+
+def __get_header(f):
+    """
+    Get block header
+    :param f: buffer, required
+    :return: dict
+    """
 
-    # Read block header
-    header = block.Header()
-    header.block_hash = block_hash[::-1].hex()
-    header.version = int.from_bytes(header_bytes[:4], 'little')
-    header.previous_block_hash = header_bytes[4:36][::-1].hex()
-    header.merkle_root = header_bytes[36:68][::-1].hex()
-    header.timestamp = int.from_bytes(header_bytes[68:72], 'little')
-    header.difficult_target = int.from_bytes(header_bytes[72:76], 'little')
-    header.nonce = int.from_bytes(header_bytes[76:80], 'little')
+    header = Header()
+    header.version = int.from_bytes(f.read(4), 'little')
+    header.previous_block_hash = f.read(32)[::-1].hex()
+    header.merkle_root = f.read(32)[::-1].hex()
+    header.timestamp = int.from_bytes(f.read(4), 'little')
+    header.bits = int.from_bytes(f.read(4), 'little')
+    header.nonce = int.from_bytes(f.read(4), 'little')
+
+    return header.__dict__
+
 
-    # Number of transactions (varInt)
-    block.number_of_transactions = get_variable_int(file)
+def __get_transaction(f):
+    """
+    Get transaction
+    :param f: buffer, required
+    :return: dict
+    """
+
+    transaction = Transaction()
+    transaction.version = int.from_bytes(f.read(4)[::-1], 'big')
+    number_of_inputs = __get_variable_int(f)
+
+    for input_number in range(number_of_inputs):
+        transaction_input = TransactionInput()
+        transaction_input.id = f.read(32)[::-1].hex()
+
+        if transaction_input.id == '0000000000000000000000000000000000000000000000000000000000000000':
+            transaction_input.is_coinbase = True
+
+        transaction_input.vout = int.from_bytes(f.read(4)[::-1], 'little')
+        script_sig_size = __get_variable_int(f)
+        transaction_input.script_sig = f.read(script_sig_size).hex()
+        transaction_input.sequence = int.from_bytes(f.read(4)[::-1], 'little')
+        transaction.inputs.append(transaction_input.__dict__)
 
-    for transaction_number in range(block.number_of_transactions):
-        transaction = Transaction()
-        transaction.version = int.from_bytes(read_bytes(file, 4), 'big')
-        transaction.number_of_inputs = get_variable_int(file)
+    number_of_outputs = __get_variable_int(f)
+
+    for output_number in range(number_of_outputs):
+        transaction_output = TransactionOutput()
+        transaction_output.value = float.fromhex(f.read(8)[::-1].hex())
+        transaction_output.value /= 100000000  # Satoshis to BTC
+        script_pub_key_size = __get_variable_int(f)
+        transaction_output.script_pub_key = f.read(script_pub_key_size)
+        transaction.outputs.append(transaction_output.__dict__)
+
+    transaction.lock_time = int.from_bytes(f.read(4)[::-1], 'little')
 
-        for input_number in range(transaction.number_of_inputs):
-            transaction_input = transaction.TransactionInput()
-            transaction_input.id = read_bytes(file, 32).hex()
-            if transaction_input.id == '0000000000000000000000000000000000000000000000000000000000000000':
-                transaction_input.is_coinbase = True
+    print(transaction.outputs)
+    print(transaction.inputs)
+    print(transaction.__dict__)
+
+    return transaction.__dict__
+
+
+def __get_hash(buffer, bytes_order='backward'):
+    """
+    Compute hash from bytes
+    More info about bytes order: https://en.wikipedia.org/wiki/Endianness
+    :param buffer: bytes, required
+    :param bytes_order: string, 'backward' or 'forward', optional
+    :return: string
+    """
 
-            transaction_input.vout = int.from_bytes(read_bytes(file, 4), 'little')
-            transaction_input.script_sig_size = get_variable_int(file)
-            transaction_input.script_sig = read_bytes(file, transaction_input.script_sig_size, 'forward').hex()
-            transaction_input.sequence = int.from_bytes(read_bytes(file, 4), 'little')
+    h = hashlib.sha256(buffer).digest()
+    h = hashlib.sha256(h).digest()
+
+    if bytes_order == 'backward':
+        h = h[::-1]
+
+    return h.hex()
+
 
-        transaction.number_of_outputs = get_variable_int(file)
+def __get_variable_int(f):
+    """
+    Get variable int from transaction data
+    More info: https://learnmeabitcoin.com/technical/varint
+    :param f: buffer, required
+    :return: int
+    """
+
+    first_byte = f.read(1)
 
-        for output_number in range(transaction.number_of_outputs):
-            transaction_output = transaction.TransactionOutput()
-            transaction_output.value = float.fromhex(read_bytes(file, 8).hex())
-            transaction_output.value /= 100000000  # Satoshis to BTC
+    if first_byte == b'\xfd':
+        variable_int_bytes = f.read(2)[::-1]
+    elif first_byte == b'\xfe':
+        variable_int_bytes = f.read(4)[::-1]
+    elif first_byte == b'\xff':
+        variable_int_bytes = f.read(8)[::-1]
+    else:
+        variable_int_bytes = first_byte
+
+    return int.from_bytes(variable_int_bytes, 'little')