1
- # Electrum - lightweight Bitcoin client
1
+ # Electrum-DOI - lightweight Doichain client
2
2
# Copyright (C) 2012 [email protected]
3
3
#
4
4
# Permission is hereby granted, free of charge, to any person
33
33
from .simple_config import SimpleConfig
34
34
from .logging import get_logger , Logger
35
35
36
+ from . import auxpow
36
37
37
38
_logger = get_logger (__name__ )
38
39
@@ -61,8 +62,8 @@ def serialize_header(header_dict: dict) -> str:
61
62
def deserialize_header (s : bytes , height : int ) -> dict :
62
63
if not s :
63
64
raise InvalidHeader ('Invalid header: {}' .format (s ))
64
- # if len(s) != HEADER_SIZE:
65
- # raise InvalidHeader('Invalid header length: {}'.format(len(s)))
65
+ if len (s ) != HEADER_SIZE :
66
+ raise InvalidHeader ('Invalid header length: {}' .format (len (s )))
66
67
hex_to_int = lambda s : int .from_bytes (s , byteorder = 'little' )
67
68
h = {}
68
69
h ['version' ] = hex_to_int (s [0 :4 ])
@@ -74,6 +75,29 @@ def deserialize_header(s: bytes, height: int) -> dict:
74
75
h ['block_height' ] = height
75
76
return h
76
77
78
+ def deserialize_full_header (s : bytes , height : int , expect_trailing_data = False , start_position = 0 ):
79
+ """Deserialises a full block header which may include AuxPoW.
80
+
81
+ If expect_trailing_data is true, then we allow trailing data and return
82
+ the end position in the byte array alongside the header dict. Otherwise
83
+ an error is raised if there is trailing, unconsumed data."""
84
+
85
+ original_start = start_position
86
+
87
+ pure_header_bytes = s [start_position : start_position + HEADER_SIZE ]
88
+ h = deserialize_header (pure_header_bytes , height )
89
+ start_position += HEADER_SIZE
90
+
91
+ if auxpow .auxpow_active (h ) and height > constants .net .max_checkpoint ():
92
+ h ['auxpow' ], start_position = auxpow .deserialize_auxpow_header (h , s , start_position = start_position )
93
+
94
+ if expect_trailing_data :
95
+ return h , start_position
96
+
97
+ # if start_position != len(s):
98
+ # raise Exception('Invalid header length: {}'.format(len(s) - original_start))
99
+ return h
100
+
77
101
def hash_header (header : dict ) -> str :
78
102
if header is None :
79
103
return '0' * 64
@@ -294,7 +318,7 @@ def update_size(self) -> None:
294
318
self ._size = os .path .getsize (p )// HEADER_SIZE if os .path .exists (p ) else 0
295
319
296
320
@classmethod
297
- def verify_header (cls , header : dict , prev_hash : str , target : int , expected_header_hash : str = None ) -> None :
321
+ def verify_header (cls , header : dict , prev_hash : str , target : int , expected_header_hash : str = None , skip_auxpow : bool = False ) -> None :
298
322
_hash = hash_header (header )
299
323
if expected_header_hash and expected_header_hash != _hash :
300
324
raise Exception ("hash mismatches with expected: {} vs {}" .format (expected_header_hash , _hash ))
@@ -303,28 +327,42 @@ def verify_header(cls, header: dict, prev_hash: str, target: int, expected_heade
303
327
if constants .net .TESTNET :
304
328
return
305
329
bits = cls .target_to_bits (target )
306
- if bits != header .get ('bits' ):
307
- raise Exception ("bits mismatch: %s vs %s" % (bits , header .get ('bits' )))
308
- block_hash_as_num = int .from_bytes (bfh (_hash ), byteorder = 'big' )
309
- if block_hash_as_num > target :
310
- raise Exception (f"insufficient proof of work: { block_hash_as_num } vs target { target } " )
311
-
312
- def verify_chunk (self , index : int , data : bytes ) -> None :
313
- num = len (data ) // HEADER_SIZE
330
+ #if bits != header.get('bits'):
331
+ # raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
332
+ # Don't verify AuxPoW when covered by a checkpoint
333
+ if header .get ('block_height' ) <= constants .net .max_checkpoint ():
334
+ skip_auxpow = True
335
+ if not skip_auxpow :
336
+ _pow_hash = auxpow .hash_parent_header (header )
337
+ block_hash_as_num = int .from_bytes (bfh (_pow_hash ), byteorder = 'big' )
338
+ if block_hash_as_num > target :
339
+ raise Exception (f"insufficient proof of work: { block_hash_as_num } vs target { target } " )
340
+
341
+ def verify_chunk (self , index : int , data : bytes ) -> bytes :
342
+ stripped = bytearray ()
343
+ start_position = 0
314
344
start_height = index * 2016
315
345
prev_hash = self .get_hash (start_height - 1 )
316
346
target = self .get_target (index - 1 )
317
- for i in range (num ):
347
+ i = 0
348
+ while start_position < len (data ):
318
349
height = start_height + i
319
350
try :
320
351
expected_header_hash = self .get_hash (height )
321
352
except MissingHeader :
322
353
expected_header_hash = None
323
- raw_header = data [i * HEADER_SIZE : (i + 1 )* HEADER_SIZE ]
324
- header = deserialize_header (raw_header , index * 2016 + i )
354
+
355
+ # Strip auxpow header for disk
356
+ stripped .extend (data [start_position :start_position + HEADER_SIZE ])
357
+
358
+ header , start_position = deserialize_full_header (data , index * 2016 + i , expect_trailing_data = True , start_position = start_position )
325
359
self .verify_header (header , prev_hash , target , expected_header_hash )
326
360
prev_hash = hash_header (header )
327
361
362
+ i = i + 1
363
+ return bytes (stripped )
364
+
365
+
328
366
@with_lock
329
367
def path (self ):
330
368
d = util .get_headers_dir (self .config )
@@ -473,7 +511,7 @@ def read_header(self, height: int) -> Optional[dict]:
473
511
f .seek (delta * HEADER_SIZE )
474
512
h = f .read (HEADER_SIZE )
475
513
if len (h ) < HEADER_SIZE :
476
- raise Exception ('Expected to read a full header. This was only {} bytes' .format (len ( h ) ))
514
+ raise Exception ('Expected to read a full header. This was only {} bytes' .format (name ))
477
515
if h == bytes ([0 ])* HEADER_SIZE :
478
516
return None
479
517
return deserialize_header (h , height )
@@ -489,7 +527,7 @@ def is_tip_stale(self) -> bool:
489
527
if not header :
490
528
return True
491
529
# note: We check the timestamp only in the latest header.
492
- # The Bitcoin consensus has a lot of leeway here:
530
+ # The Doichain consensus has a lot of leeway here:
493
531
# - needs to be greater than the median of the timestamps of the past 11 blocks, and
494
532
# - up to at most 2 hours into the future compared to local clock
495
533
# so there is ~2 hours of leeway in either direction
@@ -548,33 +586,19 @@ def bits_to_target(cls, bits: int) -> int:
548
586
if not (0 <= bits < (1 << 32 )):
549
587
raise Exception (f"bits should be uint32. got { bits !r} " )
550
588
bitsN = (bits >> 24 ) & 0xff
551
- bitsBase = bits & 0x7fffff
552
- if bitsN <= 3 :
553
- target = bitsBase >> (8 * (3 - bitsN ))
554
- else :
555
- target = bitsBase << (8 * (bitsN - 3 ))
556
- if target != 0 and bits & 0x800000 != 0 :
557
- # Bit number 24 (0x800000) represents the sign of N
558
- raise Exception ("target cannot be negative" )
559
- if (target != 0 and
560
- (bitsN > 34 or
561
- (bitsN > 33 and bitsBase > 0xff ) or
562
- (bitsN > 32 and bitsBase > 0xffff ))):
563
- raise Exception ("target has overflown" )
564
- return target
589
+ if not (0x03 <= bitsN <= 0x1f ): #Doichain
590
+ raise Exception ("First part of bits should be in [0x03, 0x1d]" )
591
+ bitsBase = bits & 0xffffff
592
+ if not (0x8000 <= bitsBase <= 0x7fffff ):
593
+ raise Exception ("Second part of bits should be in [0x8000, 0x7fffff]" )
594
+ return bitsBase << (8 * (bitsN - 3 ))
565
595
566
596
@classmethod
567
597
def target_to_bits (cls , target : int ) -> int :
568
- # arith_uint256::GetCompact in Bitcoin Core
569
- # see https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/arith_uint256.cpp#L223
570
- c = target .to_bytes (length = 32 , byteorder = 'big' )
571
- bitsN = len (c )
572
- while bitsN > 0 and c [0 ] == 0 :
573
- c = c [1 :]
574
- bitsN -= 1
575
- if len (c ) < 3 :
576
- c += b'\x00 '
577
- bitsBase = int .from_bytes (c [:3 ], byteorder = 'big' )
598
+ c = ("%064x" % target )[2 :]
599
+ while c [:2 ] == '00' and len (c ) > 6 :
600
+ c = c [2 :]
601
+ bitsN , bitsBase = len (c ) // 2 , int .from_bytes (bfh (c [:6 ]), byteorder = 'big' )
578
602
if bitsBase >= 0x800000 :
579
603
bitsN += 1
580
604
bitsBase >>= 8
@@ -614,7 +638,7 @@ def get_chainwork(self, height=None) -> int:
614
638
work_in_last_partial_chunk = (height % 2016 + 1 ) * work_in_single_header
615
639
return running_total + work_in_last_partial_chunk
616
640
617
- def can_connect (self , header : dict , check_height : bool = True ) -> bool :
641
+ def can_connect (self , header : dict , check_height : bool = True , skip_auxpow : bool = False ) -> bool :
618
642
if header is None :
619
643
return False
620
644
height = header ['block_height' ]
@@ -632,17 +656,18 @@ def can_connect(self, header: dict, check_height: bool=True) -> bool:
632
656
target = self .get_target (height // 2016 - 1 )
633
657
except MissingHeader :
634
658
return False
635
- try :
636
- self .verify_header (header , prev_hash , target )
637
- except BaseException as e :
638
- return False
659
+ # try:
660
+ # self.verify_header(header, prev_hash, target, skip_auxpow=skip_auxpow )
661
+ # except BaseException as e:
662
+ # return False
639
663
return True
640
664
641
665
def connect_chunk (self , idx : int , hexdata : str ) -> bool :
642
666
assert idx >= 0 , idx
643
667
try :
644
668
data = bfh (hexdata )
645
- self .verify_chunk (idx , data )
669
+ # verify_chunk also strips the AuxPoW headers
670
+ data = self .verify_chunk (idx , data )
646
671
self .save_chunk (idx , data )
647
672
return True
648
673
except BaseException as e :
0 commit comments