Mercurial > public > bitcaviar-plus
comparison 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 |
comparison
equal
deleted
inserted
replaced
3:3d83609e12a1 | 4:e7a84094bf07 |
---|---|
1 import hashlib | 1 import hashlib |
2 from src.helpers import read_bytes | 2 from src.block_structure import * |
3 from src.helpers import get_variable_int | |
4 | 3 |
5 | 4 |
6 class Block: | 5 def read_block(f): |
7 """ | |
8 Block structure | |
9 """ | |
10 | |
11 block_hash = str() | |
12 magic_number = int() | |
13 size = int() | |
14 number_of_transactions = int() | |
15 transactions = [] | |
16 | |
17 class Header: | |
18 version = int() | |
19 previous_block_hash = str() | |
20 merkle_root = str() | |
21 timestamp = int() # Epoch Unix time | |
22 difficult_target = int() # Bits | |
23 nonce = int() | |
24 | |
25 | |
26 class Transaction: | |
27 id = str() | |
28 version = int() | |
29 number_of_inputs = int() | |
30 inputs = [] | |
31 number_of_outputs = int() | |
32 outputs = [] | |
33 | |
34 class TransactionInput: | |
35 id = str() | |
36 is_coinbase = False | |
37 vout = int() | |
38 script_sig_size = int() | |
39 script_sig = str() | |
40 sequence = int() | |
41 | |
42 class TransactionOutput: | |
43 value = float() | |
44 script_pub_key_size = int() | |
45 script_pub_key = str() | |
46 | |
47 | |
48 def read_block(file): | |
49 """ | 6 """ |
50 Deserialize block | 7 Deserialize block |
51 More info about block structure: https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch09.asciidoc | 8 More info about block structure: https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch09.asciidoc |
52 :param file: <class '_io.BufferedReader'>, required | 9 More info about bytes order: https://en.wikipedia.org/wiki/Endianness |
10 :param f: buffer, required | |
53 :return: | 11 :return: |
54 """ | 12 """ |
55 | 13 |
56 block = Block() | 14 block = Block() |
57 block.magic_number = int.from_bytes(read_bytes(file, 4), 'big') | 15 _ = f.read(4) # Magic number |
58 block.size = int.from_bytes(read_bytes(file, 4), 'big') | 16 _ = f.read(4)[::-1] # Block size |
17 header_bytes = f.read(80) | |
18 block.h = __get_hash(header_bytes) | |
19 f.seek(8) | |
20 block.header = __get_header(f) | |
21 number_of_transactions = __get_variable_int(f) | |
22 for transaction_number in range(number_of_transactions): | |
23 block.transactions.append(__get_transaction(f)) | |
59 | 24 |
60 # Compute block hash | 25 return block.__dict__ |
61 header_bytes = read_bytes(file, 80, 'forward') | |
62 block_hash = hashlib.sha256(header_bytes).digest() | |
63 block_hash = hashlib.sha256(block_hash).digest() | |
64 | 26 |
65 # Read block header | |
66 header = block.Header() | |
67 header.block_hash = block_hash[::-1].hex() | |
68 header.version = int.from_bytes(header_bytes[:4], 'little') | |
69 header.previous_block_hash = header_bytes[4:36][::-1].hex() | |
70 header.merkle_root = header_bytes[36:68][::-1].hex() | |
71 header.timestamp = int.from_bytes(header_bytes[68:72], 'little') | |
72 header.difficult_target = int.from_bytes(header_bytes[72:76], 'little') | |
73 header.nonce = int.from_bytes(header_bytes[76:80], 'little') | |
74 | 27 |
75 # Number of transactions (varInt) | 28 def __get_header(f): |
76 block.number_of_transactions = get_variable_int(file) | 29 """ |
30 Get block header | |
31 :param f: buffer, required | |
32 :return: dict | |
33 """ | |
77 | 34 |
78 for transaction_number in range(block.number_of_transactions): | 35 header = Header() |
79 transaction = Transaction() | 36 header.version = int.from_bytes(f.read(4), 'little') |
80 transaction.version = int.from_bytes(read_bytes(file, 4), 'big') | 37 header.previous_block_hash = f.read(32)[::-1].hex() |
81 transaction.number_of_inputs = get_variable_int(file) | 38 header.merkle_root = f.read(32)[::-1].hex() |
39 header.timestamp = int.from_bytes(f.read(4), 'little') | |
40 header.bits = int.from_bytes(f.read(4), 'little') | |
41 header.nonce = int.from_bytes(f.read(4), 'little') | |
82 | 42 |
83 for input_number in range(transaction.number_of_inputs): | 43 return header.__dict__ |
84 transaction_input = transaction.TransactionInput() | |
85 transaction_input.id = read_bytes(file, 32).hex() | |
86 if transaction_input.id == '0000000000000000000000000000000000000000000000000000000000000000': | |
87 transaction_input.is_coinbase = True | |
88 | 44 |
89 transaction_input.vout = int.from_bytes(read_bytes(file, 4), 'little') | |
90 transaction_input.script_sig_size = get_variable_int(file) | |
91 transaction_input.script_sig = read_bytes(file, transaction_input.script_sig_size, 'forward').hex() | |
92 transaction_input.sequence = int.from_bytes(read_bytes(file, 4), 'little') | |
93 | 45 |
94 transaction.number_of_outputs = get_variable_int(file) | 46 def __get_transaction(f): |
47 """ | |
48 Get transaction | |
49 :param f: buffer, required | |
50 :return: dict | |
51 """ | |
95 | 52 |
96 for output_number in range(transaction.number_of_outputs): | 53 transaction = Transaction() |
97 transaction_output = transaction.TransactionOutput() | 54 transaction.version = int.from_bytes(f.read(4)[::-1], 'big') |
98 transaction_output.value = float.fromhex(read_bytes(file, 8).hex()) | 55 number_of_inputs = __get_variable_int(f) |
99 transaction_output.value /= 100000000 # Satoshis to BTC | 56 |
57 for input_number in range(number_of_inputs): | |
58 transaction_input = TransactionInput() | |
59 transaction_input.id = f.read(32)[::-1].hex() | |
60 | |
61 if transaction_input.id == '0000000000000000000000000000000000000000000000000000000000000000': | |
62 transaction_input.is_coinbase = True | |
63 | |
64 transaction_input.vout = int.from_bytes(f.read(4)[::-1], 'little') | |
65 script_sig_size = __get_variable_int(f) | |
66 transaction_input.script_sig = f.read(script_sig_size).hex() | |
67 transaction_input.sequence = int.from_bytes(f.read(4)[::-1], 'little') | |
68 transaction.inputs.append(transaction_input.__dict__) | |
69 | |
70 number_of_outputs = __get_variable_int(f) | |
71 | |
72 for output_number in range(number_of_outputs): | |
73 transaction_output = TransactionOutput() | |
74 transaction_output.value = float.fromhex(f.read(8)[::-1].hex()) | |
75 transaction_output.value /= 100000000 # Satoshis to BTC | |
76 script_pub_key_size = __get_variable_int(f) | |
77 transaction_output.script_pub_key = f.read(script_pub_key_size) | |
78 transaction.outputs.append(transaction_output.__dict__) | |
79 | |
80 transaction.lock_time = int.from_bytes(f.read(4)[::-1], 'little') | |
81 | |
82 print(transaction.outputs) | |
83 print(transaction.inputs) | |
84 print(transaction.__dict__) | |
85 | |
86 return transaction.__dict__ | |
87 | |
88 | |
89 def __get_hash(buffer, bytes_order='backward'): | |
90 """ | |
91 Compute hash from bytes | |
92 More info about bytes order: https://en.wikipedia.org/wiki/Endianness | |
93 :param buffer: bytes, required | |
94 :param bytes_order: string, 'backward' or 'forward', optional | |
95 :return: string | |
96 """ | |
97 | |
98 h = hashlib.sha256(buffer).digest() | |
99 h = hashlib.sha256(h).digest() | |
100 | |
101 if bytes_order == 'backward': | |
102 h = h[::-1] | |
103 | |
104 return h.hex() | |
105 | |
106 | |
107 def __get_variable_int(f): | |
108 """ | |
109 Get variable int from transaction data | |
110 More info: https://learnmeabitcoin.com/technical/varint | |
111 :param f: buffer, required | |
112 :return: int | |
113 """ | |
114 | |
115 first_byte = f.read(1) | |
116 | |
117 if first_byte == b'\xfd': | |
118 variable_int_bytes = f.read(2)[::-1] | |
119 elif first_byte == b'\xfe': | |
120 variable_int_bytes = f.read(4)[::-1] | |
121 elif first_byte == b'\xff': | |
122 variable_int_bytes = f.read(8)[::-1] | |
123 else: | |
124 variable_int_bytes = first_byte | |
125 | |
126 return int.from_bytes(variable_int_bytes, 'little') |