Skip to content

Commit 794cd78

Browse files
committed
display merkle root and previous hash in hex
1 parent 7b4ff01 commit 794cd78

File tree

1 file changed

+97
-83
lines changed

1 file changed

+97
-83
lines changed

bitcoinutils/block.py

Lines changed: 97 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
11
import struct
22
import hashlib
3-
from typing import Optional,Union
4-
from datetime import datetime,timezone
3+
from typing import Optional, Union
4+
from datetime import datetime, timezone
55

6-
from bitcoinutils.utils import (
7-
h_to_b,
8-
parse_compact_size,
9-
get_transaction_length
10-
)
6+
from bitcoinutils.utils import h_to_b, parse_compact_size, get_transaction_length
117

12-
from bitcoinutils.transactions import(
13-
Transaction
14-
)
8+
from bitcoinutils.transactions import Transaction
159

16-
from bitcoinutils.constants import(
17-
HEADER_SIZE,
18-
BLOCK_MAGIC_NUMBER
19-
)
10+
from bitcoinutils.constants import HEADER_SIZE, BLOCK_MAGIC_NUMBER
2011

2112
import os
2213

14+
2315
class BlockHeader:
2416
"""
2517
Represents a Bitcoin block header. This class encapsulates the details of a block's header
@@ -68,13 +60,13 @@ class BlockHeader:
6860

6961
def __init__(
7062
self,
71-
version : Optional[int] = None,
72-
previous_block_hash : Optional[bytes] = None,
73-
merkle_root : Optional[bytes] = None,
74-
timestamp : Optional[int] = None,
75-
target_bits : Optional[int] = None,
76-
nonce : Optional[int] = None
77-
) -> None :
63+
version: Optional[int] = None,
64+
previous_block_hash: Optional[bytes] = None,
65+
merkle_root: Optional[bytes] = None,
66+
timestamp: Optional[int] = None,
67+
target_bits: Optional[int] = None,
68+
nonce: Optional[int] = None,
69+
) -> None:
7870
"""
7971
Initializes a new BlockHeader object with specified attributes for a Bitcoin block.
8072
@@ -117,22 +109,29 @@ def from_raw(rawhexdata: Union[str, bytes]):
117109
rawdata = rawhexdata
118110
else:
119111
raise TypeError("Input must be a hexadecimal string or bytes")
120-
121-
#format String for struct packing/unpacking for block header
122-
header_format = '<' # little-edian
123-
header_format += 'I' # version (4 bytes)
124-
header_format += '32s' # previous block hash (32 bytes)
125-
header_format += '32s' # merkle root (32 bytes)
126-
header_format += 'I' # timestamp (4 bytes)
127-
header_format += 'I' # target bits (4 bytes)
128-
header_format += 'I' # nonce (4 bytes)
112+
113+
# format String for struct packing/unpacking for block header
114+
header_format = "<" # little-edian
115+
header_format += "I" # version (4 bytes)
116+
header_format += "32s" # previous block hash (32 bytes)
117+
header_format += "32s" # merkle root (32 bytes)
118+
header_format += "I" # timestamp (4 bytes)
119+
header_format += "I" # target bits (4 bytes)
120+
header_format += "I" # nonce (4 bytes)
129121

130122
if len(rawdata) != HEADER_SIZE:
131123
raise ValueError(f"Incorrect data length. Expected {HEADER_SIZE} bytes.")
132-
133-
version,previous_block_hash,merkle_root,timestamp,target_bits,nonce= struct.unpack(header_format,rawdata)
134-
previous_block_hash = previous_block_hash[::-1] #natural byte order
135-
merkle_root = merkle_root[::-1] #natural byte order
124+
125+
(
126+
version,
127+
previous_block_hash,
128+
merkle_root,
129+
timestamp,
130+
target_bits,
131+
nonce,
132+
) = struct.unpack(header_format, rawdata)
133+
previous_block_hash = previous_block_hash[::-1] # natural byte order
134+
merkle_root = merkle_root[::-1] # natural byte order
136135

137136
return BlockHeader(
138137
version=version,
@@ -142,7 +141,7 @@ def from_raw(rawhexdata: Union[str, bytes]):
142141
target_bits=target_bits,
143142
nonce=nonce,
144143
)
145-
144+
146145
def __str__(self) -> str:
147146
"""
148147
Returns a string representation of the BlockHeader, presenting all header information in a readable format.
@@ -164,18 +163,18 @@ def __str__(self) -> str:
164163

165164
def __repr__(self) -> str:
166165
return self.__str__()
167-
166+
168167
def get_version(self) -> Optional[int]:
169168
"""Returns the block version, or None if not set."""
170169
return self.version if self.version is not None else None
171170

172171
def get_previous_block_hash(self) -> Optional[bytes]:
173172
"""Returns the previous block hash as bytes, or None if not set."""
174-
return self.previous_block_hash if self.previous_block_hash else None
173+
return self.previous_block_hash.hex() if self.previous_block_hash else None
175174

176175
def get_merkle_root(self) -> Optional[bytes]:
177176
"""Returns the merkle root as bytes, or None if not set."""
178-
return self.merkle_root if self.merkle_root else None
177+
return self.merkle_root.hex() if self.merkle_root else None
179178

180179
def get_timestamp(self) -> Optional[int]:
181180
"""Returns the block timestamp, or None if not set."""
@@ -188,32 +187,32 @@ def get_target_bits(self) -> Optional[int]:
188187
def get_nonce(self) -> Optional[int]:
189188
"""Returns the nonce used for mining, or None if not set."""
190189
return self.nonce if self.nonce is not None else None
191-
190+
192191
def format_timestamp(self):
193192
"""
194193
Formats the block's timestamp into a human-readable UTC datetime string.
195194
196195
Returns:
197196
str: The formatted UTC datetime string representing the block's timestamp.
198197
"""
199-
198+
200199
# A timezone-aware datetime object in UTC
201200
utc_time = datetime.fromtimestamp(self.timestamp, timezone.utc)
202201
return utc_time.strftime("%Y-%m-%d %H:%M:%S UTC")
203-
202+
204203
def get_target_bits(self):
205204
"""
206205
Decodes the compact representation of the target bits into the full target hash
207206
that a block's hash must be less than or equal to, in order to solve the block.
208-
207+
209208
Returns:
210209
str: The target value as a 64-character hexadecimal string, representing
211210
the full 256-bit target hash used in the proof of work.
212211
"""
213-
212+
214213
# Extract the exponent (first byte) and coefficient (last three bytes) from the target_bits
215214
exponent = self.target_bits >> 24
216-
coefficient = self.target_bits & 0xffffff
215+
coefficient = self.target_bits & 0xFFFFFF
217216

218217
# Calculate the target by shifting the coefficient by (exponent - 3) * 8 bits to the left
219218
target = coefficient << (8 * (exponent - 3))
@@ -225,17 +224,25 @@ def get_target_bits(self):
225224
def serialize_header(self):
226225
"""Serializes the block header in the format required for hashing."""
227226
# Ensure previous_block_hash and merkle_root are in byte form
228-
prev_hash = self.previous_block_hash if isinstance(self.previous_block_hash, bytes) else bytes.fromhex(self.previous_block_hash)
229-
merkle_root = self.merkle_root if isinstance(self.merkle_root, bytes) else bytes.fromhex(self.merkle_root)
227+
prev_hash = (
228+
self.previous_block_hash
229+
if isinstance(self.previous_block_hash, bytes)
230+
else bytes.fromhex(self.previous_block_hash)
231+
)
232+
merkle_root = (
233+
self.merkle_root
234+
if isinstance(self.merkle_root, bytes)
235+
else bytes.fromhex(self.merkle_root)
236+
)
230237

231238
# Block header data is serialized in little-endian byte order
232239
header_data = (
233-
struct.pack('<I', self.version) +
234-
prev_hash[::-1] + # reverse to little-endian
235-
merkle_root[::-1] + # reverse to little-endian
236-
struct.pack('<I', self.timestamp) +
237-
struct.pack('<I', self.target_bits) +
238-
struct.pack('<I', self.nonce)
240+
struct.pack("<I", self.version)
241+
+ prev_hash[::-1]
242+
+ merkle_root[::-1] # reverse to little-endian
243+
+ struct.pack("<I", self.timestamp) # reverse to little-endian
244+
+ struct.pack("<I", self.target_bits)
245+
+ struct.pack("<I", self.nonce)
239246
)
240247
return header_data
241248

@@ -247,6 +254,7 @@ def get_block_hash(self):
247254
# Bitcoin block hashes are displayed in big-endian hex, but calculated in little-endian
248255
return hash_two[::-1].hex() # reverse to big-endian and convert to hex string
249256

257+
250258
class Block:
251259
"""
252260
Represents a Bitcoin block, encapsulating the block's fundamental components including
@@ -304,11 +312,11 @@ class Block:
304312

305313
def __init__(
306314
self,
307-
magic : Optional[bytes] = None,
308-
block_size : Optional[int] = None,
309-
header : Optional[BlockHeader] = None,
310-
transaction_count : Optional[int] = None,
311-
transactions : Optional[list[Transaction]] = None
315+
magic: Optional[bytes] = None,
316+
block_size: Optional[int] = None,
317+
header: Optional[BlockHeader] = None,
318+
transaction_count: Optional[int] = None,
319+
transactions: Optional[list[Transaction]] = None,
312320
):
313321
"""
314322
Initializes a new instance of Block, setting up all the necessary attributes that define a Bitcoin block.
@@ -328,7 +336,7 @@ def __init__(
328336
self.transactions = transactions
329337

330338
@staticmethod
331-
def from_raw(rawhexdata : Union[str,bytes]):
339+
def from_raw(rawhexdata: Union[str, bytes]):
332340
"""
333341
Constructs a Block instance from raw block data in hexadecimal or byte format.
334342
@@ -351,42 +359,48 @@ def from_raw(rawhexdata : Union[str,bytes]):
351359
else:
352360
raise TypeError("Input must be a hexadecimal string or bytes")
353361
magic = rawdata[0:4]
354-
block_size = struct.unpack('<I', rawdata[4:8])[0]
355-
block_size = block_size
356-
header = BlockHeader.from_raw(rawdata[8:8 + HEADER_SIZE])
362+
block_size = struct.unpack("<I", rawdata[4:8])[0]
363+
block_size = block_size
364+
header = BlockHeader.from_raw(rawdata[8 : 8 + HEADER_SIZE])
357365

358366
# Handling the transaction counter which is a CompactSize
359367
transaction_count, tx_offset = parse_compact_size(rawdata[88:])
360368
transactions = []
361369
current_offset = 88 + tx_offset
362370
for i in range(transaction_count):
363371
try:
364-
tx_length = get_transaction_length(rawdata[current_offset:])
365-
transactions.append(Transaction.from_raw(rawdata[current_offset:current_offset + tx_length].hex()))
366-
temp = Transaction.from_raw(rawdata[current_offset:current_offset + tx_length].hex())
372+
tx_length = get_transaction_length(rawdata[current_offset:])
373+
transactions.append(
374+
Transaction.from_raw(
375+
rawdata[current_offset : current_offset + tx_length].hex()
376+
)
377+
)
378+
temp = Transaction.from_raw(
379+
rawdata[current_offset : current_offset + tx_length].hex()
380+
)
367381
current_offset += tx_length
368-
382+
369383
except Exception as e:
370384
print(e)
371-
print(i,transaction_count)
385+
print(i, transaction_count)
372386
break
373387

374388
return Block(magic, block_size, header, transaction_count, transactions)
375-
389+
376390
def __str__(self) -> str:
377391
return str(
378392
{
379393
"magic": self.magic.hex(),
380394
"block size": self.block_size,
381395
"block header": self.header,
382396
"trasaction count": self.transaction_count,
383-
"transactions": self.transactions
397+
"transactions": self.transactions,
384398
}
385399
)
386400

387401
def __repr__(self) -> str:
388402
return self.__str__()
389-
403+
390404
def get_magic_bytes(self) -> tuple[str, str]:
391405
"""
392406
Retrieves the magic bytes and its corresponding network description.
@@ -402,8 +416,8 @@ def get_magic_bytes(self) -> tuple[str, str]:
402416
raise ValueError("Magic bytes are not set.")
403417

404418
magic_hex = self.magic.hex()
405-
return (magic_hex,BLOCK_MAGIC_NUMBER[magic_hex])
406-
419+
return (magic_hex, BLOCK_MAGIC_NUMBER[magic_hex])
420+
407421
def get_block_size(self) -> int:
408422
"""
409423
Retrieves the size of the block in bytes.
@@ -414,7 +428,7 @@ def get_block_size(self) -> int:
414428

415429
return self.block_size
416430

417-
def get_block_header(self) -> BlockHeader :
431+
def get_block_header(self) -> BlockHeader:
418432
"""
419433
Retrieves the header of the block.
420434
@@ -427,7 +441,7 @@ def get_block_header(self) -> BlockHeader :
427441
if self.header is None:
428442
raise ValueError("Block header is not set.")
429443
return self.header
430-
444+
431445
def get_transactions(self) -> list[Transaction]:
432446
"""
433447
Retrieves the transactions contained in the block.
@@ -442,8 +456,8 @@ def get_transactions(self) -> list[Transaction]:
442456
if self.transactions is None:
443457
raise ValueError("No transactions given.")
444458
return self.transactions
445-
446-
def get_transactions_count(self) -> int :
459+
460+
def get_transactions_count(self) -> int:
447461
"""
448462
Retrieves the number of transactions in the block.
449463
@@ -457,7 +471,7 @@ def get_transactions_count(self) -> int :
457471
if self.transactions is None:
458472
raise ValueError("No transactions given.")
459473
return self.transaction_count
460-
474+
461475
def get_coinbase_transaction(self) -> Transaction:
462476
"""
463477
Retrieves the coinbase transaction from the block, which is the first transaction in the block and contains the block reward.
@@ -487,14 +501,14 @@ def get_block_reward(self) -> int:
487501
if self.transactions is None:
488502
raise ValueError("No transactions given.")
489503
coinbase = self.get_coinbase_transaction()
490-
504+
491505
amount = 0
492506

493-
for output in coinbase.outputs :
507+
for output in coinbase.outputs:
494508
amount += output.amount
495509

496510
return amount
497-
511+
498512
def get_witness_transactions(self) -> list[Transaction]:
499513
"""
500514
Returns a list of transactions that contain SegWit data.
@@ -506,10 +520,10 @@ def get_witness_transactions(self) -> list[Transaction]:
506520
"""
507521
if self.transactions is None:
508522
raise ValueError("No transactions given.")
509-
523+
510524
witness_transactions = [tx for tx in self.transactions if tx.has_segwit]
511525
return witness_transactions
512-
526+
513527
def get_legacy_transactions(self) -> list[Transaction]:
514528
"""
515529
Returns a list of legacy transactions.
@@ -521,6 +535,6 @@ def get_legacy_transactions(self) -> list[Transaction]:
521535
"""
522536
if self.transactions is None:
523537
raise ValueError("No transactions given.")
524-
538+
525539
legacy_transactions = [tx for tx in self.transactions if not tx.has_segwit]
526-
return legacy_transactions
540+
return legacy_transactions

0 commit comments

Comments
 (0)