11import struct
22import 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
2112import os
2213
14+
2315class 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+
250258class 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