Skip to content

Commit 2a5b295

Browse files
hTrapcryptoprofutonium
authored andcommitted
Patch electrum to support Bubbles network upgrade (#17)
* Patch electrum to support Bubbles network upgrade Summary ======= - Handle dynamic header size - Update the branch id used for signing the transaction - Handle verification of blocks following the difficulty adjustment - Update block explorers link - Add new electrum server ip
1 parent e9655d3 commit 2a5b295

File tree

5 files changed

+130
-59
lines changed

5 files changed

+130
-59
lines changed

lib/blockchain.py

Lines changed: 112 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# SOFTWARE.
2323
import os
2424
import threading
25+
from time import sleep
2526

2627
from . import util
2728
from . import bitcoin
@@ -32,6 +33,7 @@
3233
HDR_EH_192_7_LEN = 543
3334
CHUNK_LEN = 100
3435
BUBBLES_ACTIVATION_HEIGHT = 585318
36+
DIFFADJ_ACTIVATION_HEIGHT = 585322
3537

3638
MAX_TARGET = 0x0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
3739
POW_AVERAGING_WINDOW = 17
@@ -52,6 +54,14 @@
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+
5565
def 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):
6777
def 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

lib/network.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from . import util
3838
from . import bitcoin
3939
from .bitcoin import *
40-
from .blockchain import HDR_LEN, HDR_EH_192_7_LEN, CHUNK_LEN
40+
from .blockchain import CHUNK_LEN, get_header_size
4141
from . import constants
4242
from .interface import Connection, Interface
4343
from . import blockchain
@@ -831,7 +831,7 @@ def on_get_header(self, interface, response, height):
831831
self.connection_down(interface.server)
832832
return
833833

834-
if len(hex_header) != HDR_LEN*2 and len(hex_header) != HDR_EH_192_7_LEN*2:
834+
if len(hex_header) != get_header_size(height)*2:
835835
interface.print_error('wrong header length', interface.request)
836836
self.connection_down(interface.server)
837837
return
@@ -984,17 +984,19 @@ def wait_on_sockets(self):
984984
def init_headers_file(self):
985985
b = self.blockchains[0]
986986
filename = b.path()
987-
len_checkpoints = len(constants.net.CHECKPOINTS)
988-
length = HDR_LEN * len_checkpoints * CHUNK_LEN
989-
if not os.path.exists(filename) or os.path.getsize(filename) < length:
987+
# len_checkpoints = len(constants.net.CHECKPOINTS)
988+
len_checkpoints = 0
989+
# length = HDR_LEN * len_checkpoints * CHUNK_LEN
990+
# if not os.path.exists(filename) or os.path.getsize(filename) < length:
991+
if not os.path.exists(filename):
990992
with open(filename, 'wb') as f:
991993
for i in range(len_checkpoints):
992994
for height, header_data in b.checkpoints[i][2]:
993-
f.seek(height*HDR_LEN)
995+
f.seek(height*get_header_size(height))
994996
bin_header = bfh(header_data)
995997
f.write(bin_header)
996998
with b.lock:
997-
b.update_size()
999+
b.update_size(0)
9981000

9991001
def run(self):
10001002
self.init_headers_file()
@@ -1013,10 +1015,10 @@ def on_notify_header(self, interface, header):
10131015
if not height or not hex_header:
10141016
return
10151017

1016-
if len(hex_header) != HDR_LEN*2 and len(hex_header) != HDR_EH_192_7_LEN*2:
1017-
interface.print_error('wrong header length', interface.request)
1018-
self.connection_down(interface.server)
1019-
return
1018+
# if len(hex_header) != get_header_size(height)*2:
1019+
# interface.print_error('wrong header length', interface.request)
1020+
# self.connection_down(interface.server)
1021+
# return
10201022

10211023
header = blockchain.deserialize_header(bfh(hex_header), height)
10221024
if height < self.max_checkpoint():

lib/servers.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"electrum.zclassic-ce.org": {
2+
"178.128.27.40": {
33
"pruning": "-",
44
"s": "50002",
55
"t": "50001",

lib/util.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -506,10 +506,8 @@ def time_difference(distance_in_time, include_seconds):
506506
return "over %d years" % (round(distance_in_minutes / 525600))
507507

508508
mainnet_block_explorers = {
509-
'zclele.duckdns.org:3001': ('http://zclele.duckdns.org:3001',
510-
{'tx': 'tx', 'addr': 'address'}),
511-
'zclassic-ce.io': ('http://zclassic-ce.io',
512-
{'tx': 'tx', 'addr': 'address'}),
509+
'zeltrez.io': ('https://explorer.zcl.zeltrez.io/',
510+
{'tx': 'tx/', 'addr': 'address/'})
513511
}
514512

515513
testnet_block_explorers = {
@@ -524,7 +522,7 @@ def block_explorer_info():
524522
return testnet_block_explorers if constants.net.TESTNET else mainnet_block_explorers
525523

526524
def block_explorer(config):
527-
return config.get('block_explorer', 'zclassic-ce.io')
525+
return config.get('block_explorer', 'zeltrez.io')
528526

529527
def block_explorer_tuple(config):
530528
return block_explorer_info().get(block_explorer(config))

lib/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ELECTRUM_VERSION = 'v3.2.3' # version of the client package
1+
ELECTRUM_VERSION = 'v3.2.4' # version of the client package
22
PROTOCOL_VERSION = '1.2' # protocol version requested
33

44
# The hash of the mnemonic seed must begin with this

0 commit comments

Comments
 (0)