Skip to content

Commit 652b53f

Browse files
Merge pull request #27 from NyanCatTW1/master
Enable syncing above height 172158 on mainnet
2 parents f2bfa2d + 65c1de3 commit 652b53f

11 files changed

Lines changed: 369 additions & 684 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Lightweigth FactWallet
1+
# Lightweight FactWallet
22
```
33
Licence: MIT Licence
44
Language: Python (>= 3.8)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Acquire::Check-Valid-Until "0";

electrum/blockchain.py

Lines changed: 100 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,59 @@
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
"""
7175
MAINNET_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+
73109
class MissingHeader(Exception):
74110
pass
75111

@@ -199,7 +235,7 @@ def get_best_chain() -> 'Blockchain':
199235
def 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

Comments
 (0)