changeset 4:e7a84094bf07

refactor code
author Dennis Concepcion Martin <dennisconcepcionmartin@gmail.com>
date Wed, 20 Oct 2021 19:36:39 +0200
parents 3d83609e12a1
children 1a8d94b500d8
files README.md main.py src/block.py src/block_structure.py src/helpers.py test_block_0.json
diffstat 6 files changed, 164 insertions(+), 122 deletions(-) [+]
line wrap: on
line diff
--- a/README.md	Sun Oct 17 17:33:26 2021 +0200
+++ b/README.md	Wed Oct 20 19:36:39 2021 +0200
@@ -1,2 +1,7 @@
 # puppy
-A Bitcoin blockchain parser written in Python.
\ No newline at end of file
+A Bitcoin blockchain parser written in Python.
+
+## Attribution
+- [blockchain-parser](https://github.com/ragestack/blockchain-parser/blob/master/blockchain-parser.py)
+- [bitcoinbook](https://github.com/bitcoinbook/bitcoinbook)
+- [LearnMeABitcoin.com](https://learnmeabitcoin.com)
\ No newline at end of file
--- a/main.py	Sun Oct 17 17:33:26 2021 +0200
+++ b/main.py	Wed Oct 20 19:36:39 2021 +0200
@@ -2,8 +2,12 @@
 
 
 def main():
-    with open('/Users/dennis/Bitcoin/blocks/blk00000.dat', 'rb') as file:
-        read_block(file)
+    with open('/Users/dennis/Bitcoin/blocks/blk00000.dat', 'rb') as f:
+        block = read_block(f)
+
+        import json
+        with open('test_block_0.json', 'w') as f_test:
+            json.dump(block, f_test, ensure_ascii=False, indent=4)
 
 
 if __name__ == '__main__':
--- 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')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/block_structure.py	Wed Oct 20 19:36:39 2021 +0200
@@ -0,0 +1,33 @@
+class Block:
+    h = str()  # Block hash
+    header = dict()
+    transactions = []
+
+
+class Header:
+    version = int()
+    previous_block_hash = str()
+    merkle_root = str()
+    timestamp = int()
+    bits = int()
+    nonce = int()
+
+
+class Transaction:
+    version = int()
+    inputs = []
+    outputs = []
+    lock_time = int()
+
+
+class TransactionInput:
+    is_coinbase = False
+    id = str()
+    vout = int()
+    script_sig = str()
+    sequence = int()
+
+
+class TransactionOutput:
+    value = float()
+    script_pub_key = str()
\ No newline at end of file
--- a/src/helpers.py	Sun Oct 17 17:33:26 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-def read_bytes(file, number_of_bytes, bytes_order='backward'):
-    """
-    Read bytes from buffer
-    :param file: <class '_io.BufferedReader'>, required
-    :param number_of_bytes: int, required
-    :param bytes_order: string, 'backward' or 'forward', required
-    :return: string
-    """
-
-    # More info about bytes order: https://en.wikipedia.org/wiki/Endianness
-
-    b = file.read(number_of_bytes)
-    if bytes_order == 'backward':
-        b = b[::-1]
-
-    return b
-
-
-def get_variable_int(file):
-    """
-    Get variable int from transaction data
-    More info: https://learnmeabitcoin.com/technical/varint
-    :param file: <class '_io.BufferedReader'>, required
-    :return: int
-    """
-
-    first_byte = read_bytes(file, 1)
-
-    if first_byte == b'\xfd':
-        variable_int_bytes = read_bytes(file, 2)
-    elif first_byte == b'\xfe':
-        variable_int_bytes = read_bytes(file, 4)
-    elif first_byte == b'\xff':
-        variable_int_bytes = read_bytes(file, 8)
-    else:
-        variable_int_bytes = first_byte
-
-    return int.from_bytes(variable_int_bytes, 'little')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test_block_0.json	Wed Oct 20 19:36:39 2021 +0200
@@ -0,0 +1,11 @@
+{
+    "h": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
+    "header": {
+        "version": 1,
+        "previous_block_hash": "0000000000000000000000000000000000000000000000000000000000000000",
+        "merkle_root": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
+        "timestamp": 1231006505,
+        "bits": 486604799,
+        "nonce": 2083236893
+    }
+}
\ No newline at end of file