5353 "softforks": {
5454 (...)
5555 "hard_diff_removal": {
56+ "type": "buried",
57+ "active": true,
58+ "height": 168672
59+ },
60+ "interim_daa": {
5661 "type": "bip9",
5762 "bip9": {
58- "status": "active ",
59- "start_time": 1743465600 ,
60- "timeout": 1775001600 ,
61- "since": 168672 ,
62- "min_activation_height": 160000
63+ "status": "locked_in ",
64+ "start_time": 1767225600 ,
65+ "timeout": 1784505600 ,
66+ "since": 172116 ,
67+ "min_activation_height": 0
6368 },
64- "height": 168672,
65- "active": true
69+ "active": false
6670 }
6771 },
68- "warnings": ""
72+ (...)
6973}
7074"""
7175MAINNET_HARD_DIFF_REMOVAL_ACTIVATION_HEIGHT = 168672
7276
77+ MAINNET_INTERIM_DAA_ACTIVATION_HEIGHT = 172116 + 42 # 172158
78+ MAINNET_INTERIM_DAA_END_HEIGHT = MAINNET_INTERIM_DAA_ACTIVATION_HEIGHT + 1344 # 173502
79+ INTERIM_DAA_PERIOD = 42
80+ INTERIM_DAA_TARGET_TIMESPAN = (INTERIM_DAA_PERIOD - 1 ) * 30 * 60 # 73800 seconds
81+
82+
83+ def is_interim_daa_active (height : int ) -> bool :
84+ return MAINNET_INTERIM_DAA_ACTIVATION_HEIGHT <= height < MAINNET_INTERIM_DAA_END_HEIGHT
85+
86+
87+ def calculate_interim_daa_delta (nBits : int , proportion : float ) -> int :
88+ # See CalculateInterimDifficultyDelta in src/pow.cpp
89+ delta = 0
90+ if nBits % 2 != 0 :
91+ delta -= 1
92+
93+ if proportion < 0.5 :
94+ delta += 6
95+ elif proportion < 0.6667 :
96+ delta += 4
97+ elif proportion < 0.9 :
98+ delta += 2
99+ elif proportion > 2.0 :
100+ delta -= 6
101+ elif proportion > 1.5 :
102+ delta -= 4
103+ elif proportion > 1.0333 :
104+ delta -= 2
105+
106+ return delta
107+
108+
73109class MissingHeader (Exception ):
74110 pass
75111
@@ -199,7 +235,7 @@ def get_best_chain() -> 'Blockchain':
199235def init_headers_file_for_best_chain ():
200236 b = get_best_chain ()
201237 filename = b .path ()
202- length = HEADER_SIZE * len (constants .net .CHECKPOINTS ) * 672
238+ length = HEADER_SIZE * len (constants .net .CHECKPOINTS ) * constants . CHUNK_SIZE
203239 if not os .path .exists (filename ) or os .path .getsize (filename ) < length :
204240 with open (filename , 'wb' ) as f :
205241 if length > 0 :
@@ -378,7 +414,7 @@ def verify_header(cls, header: dict, prev_hash: str, target: int, expected_heade
378414
379415 def verify_chunk (self , index : int , data : bytes ) -> None :
380416 num = len (data ) // HEADER_SIZE
381- start_height = index * 672
417+ start_height = index * constants . CHUNK_SIZE
382418 prev_hash = self .get_hash (start_height - 1 )
383419 target = self .get_target (index - 1 )
384420 for i in range (num ):
@@ -388,7 +424,7 @@ def verify_chunk(self, index: int, data: bytes) -> None:
388424 except MissingHeader :
389425 expected_header_hash = None
390426 raw_header = data [i * HEADER_SIZE : (i + 1 )* HEADER_SIZE ]
391- header = deserialize_header (raw_header , index * 672 + i )
427+ header = deserialize_header (raw_header , index * constants . CHUNK_SIZE + i )
392428 self .verify_header (header , prev_hash , target , expected_header_hash )
393429 prev_hash = hash_header (header )
394430
@@ -415,7 +451,7 @@ def save_chunk(self, index: int, chunk: bytes):
415451 main_chain .save_chunk (index , chunk )
416452 return
417453
418- delta_height = (index * 672 - self .forkpoint )
454+ delta_height = (index * constants . CHUNK_SIZE - self .forkpoint )
419455 delta_bytes = delta_height * HEADER_SIZE
420456 # if this chunk contains our forkpoint, only save the part after forkpoint
421457 # (the part before is the responsibility of the parent)
@@ -566,15 +602,15 @@ def is_tip_stale(self) -> bool:
566602 def get_hash (self , height : int ) -> str :
567603 def is_height_checkpoint ():
568604 within_cp_range = height <= constants .net .max_checkpoint ()
569- at_chunk_boundary = (height + 1 ) % 672 == 0
605+ at_chunk_boundary = (height + 1 ) % constants . CHUNK_SIZE == 0
570606 return within_cp_range and at_chunk_boundary
571607
572608 if height == - 1 :
573609 return '0000000000000000000000000000000000000000000000000000000000000000'
574610 elif height == 0 :
575611 return constants .net .GENESIS
576612 elif is_height_checkpoint ():
577- index = height // 672
613+ index = height // constants . CHUNK_SIZE
578614 h , t = self .checkpoints [index ]
579615 return h
580616 else :
@@ -583,16 +619,12 @@ def is_height_checkpoint():
583619 raise MissingHeader (height )
584620 return hash_header (header )
585621
586- def get_target (self , index : int ) -> int :
587- # compute target from chunk x, used in chunk x+1
588- if constants .net .TESTNET :
589- return 0
590- if index == - 1 :
591- return MIN_TARGET
592-
622+ def get_epoch_target (self , epoch_index : int ) -> int :
623+ """Compute target from retarget epoch epoch_index, used in epoch epoch_index+1.
624+ Preserves the existing 672-block retarget logic."""
593625 # new target
594- first = self .read_header (index * 672 )
595- last = self .read_header (index * 672 + 671 )
626+ first = self .read_header (epoch_index * constants . RETARGET_INTERVAL )
627+ last = self .read_header (epoch_index * constants . RETARGET_INTERVAL + constants . RETARGET_INTERVAL - 1 )
596628 if not first or not last :
597629 raise MissingHeader ()
598630
@@ -603,7 +635,7 @@ def get_target(self, index: int) -> int:
603635 new_target = nBits
604636
605637 # Determine if hard diff removal is active
606- firstBlockHeightThisEpoch = (index + 1 ) * 672
638+ firstBlockHeightThisEpoch = (epoch_index + 1 ) * constants . RETARGET_INTERVAL
607639 hardDiffRemoved = firstBlockHeightThisEpoch >= MAINNET_HARD_DIFF_REMOVAL_ACTIVATION_HEIGHT
608640
609641 # Retarget
@@ -634,6 +666,40 @@ def get_target(self, index: int) -> int:
634666
635667 return new_target
636668
669+ def get_interim_daa_target (self , chunk_index : int ) -> int :
670+ """Compute target for chunk chunk_index+1 using the 42-block interim DAA."""
671+ first_height = chunk_index * constants .CHUNK_SIZE
672+ last_height = first_height + constants .CHUNK_SIZE - 1
673+ first = self .read_header (first_height )
674+ last = self .read_header (last_height )
675+ if not first or not last :
676+ raise MissingHeader ()
677+
678+ nActualTimespan = last .get ('timestamp' ) - first .get ('timestamp' )
679+ proportion = nActualTimespan / INTERIM_DAA_TARGET_TIMESPAN
680+ nBits = int (last .get ('bits' ))
681+ delta = calculate_interim_daa_delta (nBits , proportion )
682+ return nBits + delta
683+
684+ def get_target (self , chunk_index : int ) -> int :
685+ """Returns the target applicable to chunk chunk_index+1."""
686+ if constants .net .TESTNET :
687+ return 0
688+ if chunk_index == - 1 :
689+ return MIN_TARGET
690+ if chunk_index < len (self .checkpoints ):
691+ _ , target = self .checkpoints [chunk_index ]
692+ return target
693+ next_chunk_start = (chunk_index + 1 ) * constants .CHUNK_SIZE
694+
695+ if is_interim_daa_active (next_chunk_start ):
696+ return self .get_interim_daa_target (chunk_index )
697+
698+ epoch_index = next_chunk_start // constants .RETARGET_INTERVAL
699+ if epoch_index == 0 :
700+ return MIN_TARGET
701+ return self .get_epoch_target (epoch_index - 1 )
702+
637703
638704 @classmethod
639705 def target_to_bits (cls , target : int ) -> int :
@@ -654,7 +720,7 @@ def target_to_bits(cls, target: int) -> int:
654720
655721 def chainwork_of_header_at_height (self , height : int ) -> int :
656722 """work done by single header at given height"""
657- chunk_idx = height // 672 - 1
723+ chunk_idx = height // constants . CHUNK_SIZE - 1
658724 target = self .get_target (chunk_idx )
659725 work = ((2 ** 256 - target - 1 ) // (target + 1 )) + 1
660726 return work
@@ -667,23 +733,23 @@ def get_chainwork(self, height=None) -> int:
667733 # On testnet/regtest, difficulty works somewhat different.
668734 # It's out of scope to properly implement that.
669735 return height
670- last_retarget = height // 672 * 672 - 1
736+ last_retarget = height // constants . CHUNK_SIZE * constants . CHUNK_SIZE - 1
671737 cached_height = last_retarget
672738 while _CHAINWORK_CACHE .get (self .get_hash (cached_height )) is None :
673739 if cached_height <= - 1 :
674740 break
675- cached_height -= 672
741+ cached_height -= constants . CHUNK_SIZE
676742 assert cached_height >= - 1 , cached_height
677743 running_total = _CHAINWORK_CACHE [self .get_hash (cached_height )]
678744 while cached_height < last_retarget :
679- cached_height += 672
745+ cached_height += constants . CHUNK_SIZE
680746 work_in_single_header = self .chainwork_of_header_at_height (cached_height )
681- work_in_chunk = 672 * work_in_single_header
747+ work_in_chunk = constants . CHUNK_SIZE * work_in_single_header
682748 running_total += work_in_chunk
683749 _CHAINWORK_CACHE [self .get_hash (cached_height )] = running_total
684- cached_height += 672
750+ cached_height += constants . CHUNK_SIZE
685751 work_in_single_header = self .chainwork_of_header_at_height (cached_height )
686- work_in_last_partial_chunk = (height % 672 + 1 ) * work_in_single_header
752+ work_in_last_partial_chunk = (height % constants . CHUNK_SIZE + 1 ) * work_in_single_header
687753 return running_total + work_in_last_partial_chunk
688754
689755 def can_connect (self , header : dict , check_height : bool = True ) -> bool :
@@ -701,7 +767,7 @@ def can_connect(self, header: dict, check_height: bool=True) -> bool:
701767 if prev_hash != header .get ('prev_block_hash' ):
702768 return False
703769 try :
704- target = self .get_target (height // 672 - 1 )
770+ target = self .get_target (height // constants . CHUNK_SIZE - 1 )
705771 except MissingHeader :
706772 return False
707773 try :
@@ -724,9 +790,9 @@ def connect_chunk(self, idx: int, hexdata: str) -> bool:
724790 def get_checkpoints (self ):
725791 # for each chunk, store the hash of the last block and the target after the chunk
726792 cp = []
727- n = self .height () // 672
793+ n = self .height () // constants . CHUNK_SIZE
728794 for index in range (n ):
729- h = self .get_hash ((index + 1 ) * 672 - 1 )
795+ h = self .get_hash ((index + 1 ) * constants . CHUNK_SIZE - 1 )
730796 target = self .get_target (index )
731797 cp .append ((h , target ))
732798 return cp
0 commit comments