2222# SOFTWARE.
2323import os
2424import threading
25+ from time import sleep
2526
2627from . import util
2728from . import bitcoin
3233HDR_EH_192_7_LEN = 543
3334CHUNK_LEN = 100
3435BUBBLES_ACTIVATION_HEIGHT = 585318
36+ DIFFADJ_ACTIVATION_HEIGHT = 585322
3537
3638MAX_TARGET = 0x0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
3739POW_AVERAGING_WINDOW = 17
5254 (100 + POW_MAX_ADJUST_DOWN ) // 100
5355
5456
57+ def is_post_equihash_fork (height ):
58+ return height >= BUBBLES_ACTIVATION_HEIGHT
59+
60+ def get_header_size (height ):
61+ if is_post_equihash_fork (height ):
62+ return HDR_EH_192_7_LEN
63+ return HDR_LEN
64+
5565def serialize_header (res ):
5666 s = int_to_hex (res .get ('version' ), 4 ) \
5767 + rev_hex (res .get ('prev_block_hash' )) \
@@ -67,7 +77,7 @@ def serialize_header(res):
6777def deserialize_header (s , height ):
6878 if not s :
6979 raise Exception ('Invalid header: {}' .format (s ))
70- if len (s ) != HDR_LEN and len ( s ) != HDR_EH_192_7_LEN :
80+ if len (s ) != get_header_size ( height ) :
7181 raise Exception ('Invalid header length: {}' .format (len (s )))
7282 hex_to_int = lambda s : int ('0x' + bh2u (s [::- 1 ]), 16 )
7383 h = {}
@@ -79,10 +89,7 @@ def deserialize_header(s, height):
7989 h ['bits' ] = hex_to_int (s [104 :108 ])
8090 h ['nonce' ] = hash_encode (s [108 :140 ])
8191 h ['sol_size' ] = hash_encode (s [140 :143 ])
82- if height < BUBBLES_ACTIVATION_HEIGHT :
83- h ['solution' ] = hash_encode (s [143 :HDR_LEN ])
84- else :
85- h ['solution' ] = hash_encode (s [143 :HDR_EH_192_7_LEN ])
92+ h ['solution' ] = hash_encode (s [143 :])
8693 h ['block_height' ] = height
8794 return h
8895
@@ -142,7 +149,7 @@ def __init__(self, config, checkpoint, parent_id):
142149 self .parent_id = parent_id
143150 self .lock = threading .Lock ()
144151 with self .lock :
145- self .update_size ()
152+ self .update_size (0 )
146153
147154 def parent (self ):
148155 return blockchains [self .parent_id ]
@@ -180,9 +187,33 @@ def size(self):
180187 with self .lock :
181188 return self ._size
182189
183- def update_size (self ):
190+ def update_size (self , height ):
184191 p = self .path ()
185- self ._size = os .path .getsize (p )// HDR_LEN if os .path .exists (p ) else 0
192+ if os .path .exists (p ):
193+ with open (p , 'rb' ) as f :
194+ size = f .seek (0 , 2 )
195+ self ._size = self .calculate_size (height , size )
196+ else :
197+ self ._size = 0
198+
199+ def calculate_size (self , checkpoint , size_in_bytes ):
200+ size_before_fork = 0
201+ size_after_fork = 0
202+
203+ if not is_post_equihash_fork (checkpoint ):
204+ size_before_fork = size_in_bytes // HDR_LEN
205+ if is_post_equihash_fork (size_before_fork ):
206+ size_before_fork = BUBBLES_ACTIVATION_HEIGHT
207+ checkpoint = BUBBLES_ACTIVATION_HEIGHT
208+ size_in_bytes -= size_before_fork * HDR_LEN
209+ else :
210+ size_before_fork = BUBBLES_ACTIVATION_HEIGHT
211+ size_in_bytes -= size_before_fork * HDR_LEN
212+
213+ if is_post_equihash_fork (checkpoint ):
214+ size_after_fork = size_in_bytes // HDR_EH_192_7_LEN
215+
216+ return size_before_fork + size_after_fork
186217
187218 def verify_header (self , header , prev_hash , target ):
188219 _hash = hash_header (header )
@@ -191,18 +222,29 @@ def verify_header(self, header, prev_hash, target):
191222 if constants .net .TESTNET :
192223 return
193224 bits = self .target_to_bits (target )
225+ height = header .get ('block_height' )
226+ if height >= DIFFADJ_ACTIVATION_HEIGHT and height < DIFFADJ_ACTIVATION_HEIGHT + POW_AVERAGING_WINDOW :
227+ valid_bits = [
228+ 0x1f07ffff , 0x1e0ffffe , 0x1e0ffffe , 0x1f07ffff , 0x1f014087 , 0x1f01596b ,
229+ 0x1f01743d , 0x1f019124 , 0x1f01b049 , 0x1f01d1da , 0x1f01f606 , 0x1f021d01 ,
230+ 0x1f024703 , 0x1f027448 , 0x1f02a510 , 0x1f02d9a3 , 0x1f03124a ,
231+ ]
232+ bits = valid_bits [height % DIFFADJ_ACTIVATION_HEIGHT ]
233+ target = self .bits_to_target (bits )
194234 if bits != header .get ('bits' ):
195235 raise Exception ("bits mismatch: %s vs %s" % (bits , header .get ('bits' )))
196236 if int ('0x' + _hash , 16 ) > target :
197237 raise Exception ("insufficient proof of work: %s vs target %s" % (int ('0x' + _hash , 16 ), target ))
198238
199- def verify_chunk (self , index , data ):
200- num = len (data ) // HDR_LEN
201- prev_hash = self .get_hash (index * CHUNK_LEN - 1 )
239+ def verify_chunk (self , height , data ):
240+ size = len (data )
241+ prev_hash = self .get_hash (height - 1 )
202242 chunk_headers = {'empty' : True }
203- for i in range (num ):
204- raw_header = data [i * HDR_LEN :(i + 1 ) * HDR_LEN ]
205- height = index * CHUNK_LEN + i
243+ offset = 0
244+ i = 0
245+ while offset < size :
246+ header_size = get_header_size (height )
247+ raw_header = data [offset :offset + header_size ]
206248 header = deserialize_header (raw_header , height )
207249 target = self .get_target (height , chunk_headers )
208250 self .verify_header (header , prev_hash , target )
@@ -213,20 +255,26 @@ def verify_chunk(self, index, data):
213255 chunk_headers ['empty' ] = False
214256 chunk_headers ['max_height' ] = height
215257 prev_hash = hash_header (header )
258+ offset += header_size
259+ height += 1
260+ i += 1
261+ sleep (0.001 )
216262
217263 def path (self ):
218264 d = util .get_headers_dir (self .config )
219265 filename = 'blockchain_headers' if self .parent_id is None else os .path .join ('forks' , 'fork_%d_%d' % (self .parent_id , self .checkpoint ))
220266 return os .path .join (d , filename )
221267
222- def save_chunk (self , index , chunk ):
223- filename = self .path ()
224- d = (index * CHUNK_LEN - self .checkpoint ) * HDR_LEN
225- if d < 0 :
226- chunk = chunk [- d :]
227- d = 0
228- truncate = index >= len (self .checkpoints )
229- self .write (chunk , d , truncate )
268+ def save_chunk (self , height , chunk ):
269+ delta = height - self .checkpoint
270+
271+ if delta < 0 :
272+ chunk = chunk [- delta :]
273+ height = self .checkpoint
274+
275+ offset = self .get_offset (self .checkpoint , height )
276+ truncate = (height / CHUNK_LEN ) >= len (self .checkpoints )
277+ self .write (chunk , offset , truncate )
230278 self .swap_with_parent ()
231279
232280 def swap_with_parent (self ):
@@ -241,11 +289,12 @@ def swap_with_parent(self):
241289 parent = self .parent ()
242290 with open (self .path (), 'rb' ) as f :
243291 my_data = f .read ()
292+ offset = self .get_offset (parent .checkpoint , checkpoint )
244293 with open (parent .path (), 'rb' ) as f :
245- f .seek (( checkpoint - parent . checkpoint ) * HDR_LEN )
246- parent_data = f .read (parent_branch_size * HDR_LEN )
294+ f .seek (offset )
295+ parent_data = f .read ()
247296 self .write (parent_data , 0 )
248- parent .write (my_data , ( checkpoint - parent . checkpoint ) * HDR_LEN )
297+ parent .write (my_data , offset )
249298 # store file path
250299 for b in blockchains .values ():
251300 b .old_path = b .path ()
@@ -265,23 +314,29 @@ def swap_with_parent(self):
265314
266315 def write (self , data , offset , truncate = True ):
267316 filename = self .path ()
317+ current_offset = self .get_offset (self .checkpoint , self .size ())
318+
268319 with self .lock :
269320 with open (filename , 'rb+' ) as f :
270- if truncate and offset != self . _size * HDR_LEN :
321+ if truncate and offset != current_offset :
271322 f .seek (offset )
272323 f .truncate ()
273324 f .seek (offset )
274325 f .write (data )
275326 f .flush ()
276327 os .fsync (f .fileno ())
277- self .update_size ()
328+ self .update_size (self . size () )
278329
279330 def save_header (self , header ):
280- delta = header .get ('block_height' ) - self .checkpoint
331+ height = header .get ('block_height' )
332+ delta = height - self .checkpoint
281333 data = bfh (serialize_header (header ))
334+ offset = self .get_offset (self .checkpoint , height )
335+ header_size = get_header_size (height )
336+
282337 assert delta == self .size ()
283- assert len (data ) == HDR_LEN or len ( data ) == HDR_EH_192_7_LEN
284- self .write (data , delta * HDR_LEN )
338+ assert len (data ) == header_size
339+ self .write (data , offset )
285340 self .swap_with_parent ()
286341
287342 def read_header (self , height ):
@@ -292,19 +347,20 @@ def read_header(self, height):
292347 return self .parent ().read_header (height )
293348 if height > self .height ():
294349 return
295- delta = height - self .checkpoint
350+ offset = self .get_offset (self .checkpoint , height )
351+ header_size = get_header_size (height )
296352 name = self .path ()
297353 if os .path .exists (name ):
298354 with open (name , 'rb' ) as f :
299- f .seek (delta * HDR_LEN )
300- h = f .read (HDR_LEN )
301- if len (h ) < HDR_LEN :
355+ f .seek (offset )
356+ h = f .read (header_size )
357+ if len (h ) < header_size :
302358 raise Exception ('Expected to read a full header. This was only {} bytes' .format (len (h )))
303359 elif not os .path .exists (util .get_headers_dir (self .config )):
304360 raise Exception ('Electrum datadir does not exist. Was it deleted while running?' )
305361 else :
306362 raise Exception ('Cannot find headers file but datadir is there. Should be at {}' .format (name ))
307- if h == bytes ([0 ])* HDR_LEN :
363+ if h == bytes ([0 ])* header_size :
308364 return None
309365 return deserialize_header (h , height )
310366
@@ -430,9 +486,9 @@ def can_connect(self, header, check_height=True):
430486 def connect_chunk (self , idx , hexdata ):
431487 try :
432488 data = bfh (hexdata )
433- self .verify_chunk (idx , data )
434- #self.print_error("validated chunk %d" % idx)
435- self .save_chunk (idx , data )
489+ self .verify_chunk (idx * CHUNK_LEN , data )
490+ # self.print_error("validated chunk %d" % idx)
491+ self .save_chunk (idx * CHUNK_LEN , data )
436492 return True
437493 except BaseException as e :
438494 self .print_error ('verify_chunk %d failed' % idx , str (e ))
@@ -453,12 +509,27 @@ def get_checkpoints(self):
453509 with open (self .path (), 'rb' ) as f :
454510 lower_header = height - TARGET_CALC_BLOCKS
455511 for height in range (height , lower_header - 1 , - 1 ):
456- f .seek (height * HDR_LEN )
457- hd = f .read (HDR_LEN )
458- if len (hd ) < HDR_LEN :
512+ f .seek (height * get_header_size ( height ) )
513+ hd = f .read (get_header_size ( height ) )
514+ if len (hd ) < get_header_size ( height ) :
459515 raise Exception (
460516 'Expected to read a full header.'
461517 ' This was only {} bytes' .format (len (hd )))
462518 extra_headers .append ((height , bh2u (hd )))
463519 cp .append ((h , target , extra_headers ))
464520 return cp
521+
522+ def get_offset (self , checkpoint , height ):
523+ offset_before_fork = 0
524+ offset_after_fork = 0
525+
526+ if not is_post_equihash_fork (height ):
527+ offset_before_fork = height - checkpoint
528+ else :
529+ offset_before_fork = BUBBLES_ACTIVATION_HEIGHT
530+
531+ if is_post_equihash_fork (height ):
532+ offset_after_fork = height - max (checkpoint , BUBBLES_ACTIVATION_HEIGHT )
533+
534+ offset = (offset_before_fork * HDR_LEN ) + (offset_after_fork * HDR_EH_192_7_LEN )
535+ return offset
0 commit comments