Mercurial > public > bitcaviar-plus
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