tblockchain.py - electrum - Electrum Bitcoin wallet
HTML git clone https://git.parazyd.org/electrum
DIR Log
DIR Files
DIR Refs
DIR Submodules
---
tblockchain.py (27576B)
---
1 # Electrum - lightweight Bitcoin client
2 # Copyright (C) 2012 thomasv@ecdsa.org
3 #
4 # Permission is hereby granted, free of charge, to any person
5 # obtaining a copy of this software and associated documentation files
6 # (the "Software"), to deal in the Software without restriction,
7 # including without limitation the rights to use, copy, modify, merge,
8 # publish, distribute, sublicense, and/or sell copies of the Software,
9 # and to permit persons to whom the Software is furnished to do so,
10 # subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be
13 # included in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 # SOFTWARE.
23 import os
24 import threading
25 import time
26 from typing import Optional, Dict, Mapping, Sequence
27
28 from . import util
29 from .bitcoin import hash_encode, int_to_hex, rev_hex
30 from .crypto import sha256d
31 from . import constants
32 from .util import bfh, bh2u
33 from .simple_config import SimpleConfig
34 from .logging import get_logger, Logger
35
36
37 _logger = get_logger(__name__)
38
39 HEADER_SIZE = 80 # bytes
40 MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
41
42
43 class MissingHeader(Exception):
44 pass
45
46 class InvalidHeader(Exception):
47 pass
48
49 def serialize_header(header_dict: dict) -> str:
50 s = int_to_hex(header_dict['version'], 4) \
51 + rev_hex(header_dict['prev_block_hash']) \
52 + rev_hex(header_dict['merkle_root']) \
53 + int_to_hex(int(header_dict['timestamp']), 4) \
54 + int_to_hex(int(header_dict['bits']), 4) \
55 + int_to_hex(int(header_dict['nonce']), 4)
56 return s
57
58 def deserialize_header(s: bytes, height: int) -> dict:
59 if not s:
60 raise InvalidHeader('Invalid header: {}'.format(s))
61 if len(s) != HEADER_SIZE:
62 raise InvalidHeader('Invalid header length: {}'.format(len(s)))
63 hex_to_int = lambda s: int.from_bytes(s, byteorder='little')
64 h = {}
65 h['version'] = hex_to_int(s[0:4])
66 h['prev_block_hash'] = hash_encode(s[4:36])
67 h['merkle_root'] = hash_encode(s[36:68])
68 h['timestamp'] = hex_to_int(s[68:72])
69 h['bits'] = hex_to_int(s[72:76])
70 h['nonce'] = hex_to_int(s[76:80])
71 h['block_height'] = height
72 return h
73
74 def hash_header(header: dict) -> str:
75 if header is None:
76 return '0' * 64
77 if header.get('prev_block_hash') is None:
78 header['prev_block_hash'] = '00'*32
79 return hash_raw_header(serialize_header(header))
80
81
82 def hash_raw_header(header: str) -> str:
83 return hash_encode(sha256d(bfh(header)))
84
85
86 # key: blockhash hex at forkpoint
87 # the chain at some key is the best chain that includes the given hash
88 blockchains = {} # type: Dict[str, Blockchain]
89 blockchains_lock = threading.RLock() # lock order: take this last; so after Blockchain.lock
90
91
92 def read_blockchains(config: 'SimpleConfig'):
93 best_chain = Blockchain(config=config,
94 forkpoint=0,
95 parent=None,
96 forkpoint_hash=constants.net.GENESIS,
97 prev_hash=None)
98 blockchains[constants.net.GENESIS] = best_chain
99 # consistency checks
100 if best_chain.height() > constants.net.max_checkpoint():
101 header_after_cp = best_chain.read_header(constants.net.max_checkpoint()+1)
102 if not header_after_cp or not best_chain.can_connect(header_after_cp, check_height=False):
103 _logger.info("[blockchain] deleting best chain. cannot connect header after last cp to last cp.")
104 os.unlink(best_chain.path())
105 best_chain.update_size()
106 # forks
107 fdir = os.path.join(util.get_headers_dir(config), 'forks')
108 util.make_dir(fdir)
109 # files are named as: fork2_{forkpoint}_{prev_hash}_{first_hash}
110 l = filter(lambda x: x.startswith('fork2_') and '.' not in x, os.listdir(fdir))
111 l = sorted(l, key=lambda x: int(x.split('_')[1])) # sort by forkpoint
112
113 def delete_chain(filename, reason):
114 _logger.info(f"[blockchain] deleting chain {filename}: {reason}")
115 os.unlink(os.path.join(fdir, filename))
116
117 def instantiate_chain(filename):
118 __, forkpoint, prev_hash, first_hash = filename.split('_')
119 forkpoint = int(forkpoint)
120 prev_hash = (64-len(prev_hash)) * "0" + prev_hash # left-pad with zeroes
121 first_hash = (64-len(first_hash)) * "0" + first_hash
122 # forks below the max checkpoint are not allowed
123 if forkpoint <= constants.net.max_checkpoint():
124 delete_chain(filename, "deleting fork below max checkpoint")
125 return
126 # find parent (sorting by forkpoint guarantees it's already instantiated)
127 for parent in blockchains.values():
128 if parent.check_hash(forkpoint - 1, prev_hash):
129 break
130 else:
131 delete_chain(filename, "cannot find parent for chain")
132 return
133 b = Blockchain(config=config,
134 forkpoint=forkpoint,
135 parent=parent,
136 forkpoint_hash=first_hash,
137 prev_hash=prev_hash)
138 # consistency checks
139 h = b.read_header(b.forkpoint)
140 if first_hash != hash_header(h):
141 delete_chain(filename, "incorrect first hash for chain")
142 return
143 if not b.parent.can_connect(h, check_height=False):
144 delete_chain(filename, "cannot connect chain to parent")
145 return
146 chain_id = b.get_id()
147 assert first_hash == chain_id, (first_hash, chain_id)
148 blockchains[chain_id] = b
149
150 for filename in l:
151 instantiate_chain(filename)
152
153
154 def get_best_chain() -> 'Blockchain':
155 return blockchains[constants.net.GENESIS]
156
157 # block hash -> chain work; up to and including that block
158 _CHAINWORK_CACHE = {
159 "0000000000000000000000000000000000000000000000000000000000000000": 0, # virtual block at height -1
160 } # type: Dict[str, int]
161
162
163 def init_headers_file_for_best_chain():
164 b = get_best_chain()
165 filename = b.path()
166 length = HEADER_SIZE * len(constants.net.CHECKPOINTS) * 2016
167 if not os.path.exists(filename) or os.path.getsize(filename) < length:
168 with open(filename, 'wb') as f:
169 if length > 0:
170 f.seek(length - 1)
171 f.write(b'\x00')
172 util.ensure_sparse_file(filename)
173 with b.lock:
174 b.update_size()
175
176
177 class Blockchain(Logger):
178 """
179 Manages blockchain headers and their verification
180 """
181
182 def __init__(self, config: SimpleConfig, forkpoint: int, parent: Optional['Blockchain'],
183 forkpoint_hash: str, prev_hash: Optional[str]):
184 assert isinstance(forkpoint_hash, str) and len(forkpoint_hash) == 64, forkpoint_hash
185 assert (prev_hash is None) or (isinstance(prev_hash, str) and len(prev_hash) == 64), prev_hash
186 # assert (parent is None) == (forkpoint == 0)
187 if 0 < forkpoint <= constants.net.max_checkpoint():
188 raise Exception(f"cannot fork below max checkpoint. forkpoint: {forkpoint}")
189 Logger.__init__(self)
190 self.config = config
191 self.forkpoint = forkpoint # height of first header
192 self.parent = parent
193 self._forkpoint_hash = forkpoint_hash # blockhash at forkpoint. "first hash"
194 self._prev_hash = prev_hash # blockhash immediately before forkpoint
195 self.lock = threading.RLock()
196 self.update_size()
197
198 def with_lock(func):
199 def func_wrapper(self, *args, **kwargs):
200 with self.lock:
201 return func(self, *args, **kwargs)
202 return func_wrapper
203
204 @property
205 def checkpoints(self):
206 return constants.net.CHECKPOINTS
207
208 def get_max_child(self) -> Optional[int]:
209 children = self.get_direct_children()
210 return max([x.forkpoint for x in children]) if children else None
211
212 def get_max_forkpoint(self) -> int:
213 """Returns the max height where there is a fork
214 related to this chain.
215 """
216 mc = self.get_max_child()
217 return mc if mc is not None else self.forkpoint
218
219 def get_direct_children(self) -> Sequence['Blockchain']:
220 with blockchains_lock:
221 return list(filter(lambda y: y.parent==self, blockchains.values()))
222
223 def get_parent_heights(self) -> Mapping['Blockchain', int]:
224 """Returns map: (parent chain -> height of last common block)"""
225 with self.lock, blockchains_lock:
226 result = {self: self.height()}
227 chain = self
228 while True:
229 parent = chain.parent
230 if parent is None: break
231 result[parent] = chain.forkpoint - 1
232 chain = parent
233 return result
234
235 def get_height_of_last_common_block_with_chain(self, other_chain: 'Blockchain') -> int:
236 last_common_block_height = 0
237 our_parents = self.get_parent_heights()
238 their_parents = other_chain.get_parent_heights()
239 for chain in our_parents:
240 if chain in their_parents:
241 h = min(our_parents[chain], their_parents[chain])
242 last_common_block_height = max(last_common_block_height, h)
243 return last_common_block_height
244
245 @with_lock
246 def get_branch_size(self) -> int:
247 return self.height() - self.get_max_forkpoint() + 1
248
249 def get_name(self) -> str:
250 return self.get_hash(self.get_max_forkpoint()).lstrip('0')[0:10]
251
252 def check_header(self, header: dict) -> bool:
253 header_hash = hash_header(header)
254 height = header.get('block_height')
255 return self.check_hash(height, header_hash)
256
257 def check_hash(self, height: int, header_hash: str) -> bool:
258 """Returns whether the hash of the block at given height
259 is the given hash.
260 """
261 assert isinstance(header_hash, str) and len(header_hash) == 64, header_hash # hex
262 try:
263 return header_hash == self.get_hash(height)
264 except Exception:
265 return False
266
267 def fork(parent, header: dict) -> 'Blockchain':
268 if not parent.can_connect(header, check_height=False):
269 raise Exception("forking header does not connect to parent chain")
270 forkpoint = header.get('block_height')
271 self = Blockchain(config=parent.config,
272 forkpoint=forkpoint,
273 parent=parent,
274 forkpoint_hash=hash_header(header),
275 prev_hash=parent.get_hash(forkpoint-1))
276 self.assert_headers_file_available(parent.path())
277 open(self.path(), 'w+').close()
278 self.save_header(header)
279 # put into global dict. note that in some cases
280 # save_header might have already put it there but that's OK
281 chain_id = self.get_id()
282 with blockchains_lock:
283 blockchains[chain_id] = self
284 return self
285
286 @with_lock
287 def height(self) -> int:
288 return self.forkpoint + self.size() - 1
289
290 @with_lock
291 def size(self) -> int:
292 return self._size
293
294 @with_lock
295 def update_size(self) -> None:
296 p = self.path()
297 self._size = os.path.getsize(p)//HEADER_SIZE if os.path.exists(p) else 0
298
299 @classmethod
300 def verify_header(cls, header: dict, prev_hash: str, target: int, expected_header_hash: str=None) -> None:
301 _hash = hash_header(header)
302 if expected_header_hash and expected_header_hash != _hash:
303 raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash))
304 if prev_hash != header.get('prev_block_hash'):
305 raise Exception("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')))
306 if constants.net.TESTNET:
307 return
308 bits = cls.target_to_bits(target)
309 if bits != header.get('bits'):
310 raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
311 block_hash_as_num = int.from_bytes(bfh(_hash), byteorder='big')
312 if block_hash_as_num > target:
313 raise Exception(f"insufficient proof of work: {block_hash_as_num} vs target {target}")
314
315 def verify_chunk(self, index: int, data: bytes) -> None:
316 num = len(data) // HEADER_SIZE
317 start_height = index * 2016
318 prev_hash = self.get_hash(start_height - 1)
319 target = self.get_target(index-1)
320 for i in range(num):
321 height = start_height + i
322 try:
323 expected_header_hash = self.get_hash(height)
324 except MissingHeader:
325 expected_header_hash = None
326 raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE]
327 header = deserialize_header(raw_header, index*2016 + i)
328 self.verify_header(header, prev_hash, target, expected_header_hash)
329 prev_hash = hash_header(header)
330
331 @with_lock
332 def path(self):
333 d = util.get_headers_dir(self.config)
334 if self.parent is None:
335 filename = 'blockchain_headers'
336 else:
337 assert self.forkpoint > 0, self.forkpoint
338 prev_hash = self._prev_hash.lstrip('0')
339 first_hash = self._forkpoint_hash.lstrip('0')
340 basename = f'fork2_{self.forkpoint}_{prev_hash}_{first_hash}'
341 filename = os.path.join('forks', basename)
342 return os.path.join(d, filename)
343
344 @with_lock
345 def save_chunk(self, index: int, chunk: bytes):
346 assert index >= 0, index
347 chunk_within_checkpoint_region = index < len(self.checkpoints)
348 # chunks in checkpoint region are the responsibility of the 'main chain'
349 if chunk_within_checkpoint_region and self.parent is not None:
350 main_chain = get_best_chain()
351 main_chain.save_chunk(index, chunk)
352 return
353
354 delta_height = (index * 2016 - self.forkpoint)
355 delta_bytes = delta_height * HEADER_SIZE
356 # if this chunk contains our forkpoint, only save the part after forkpoint
357 # (the part before is the responsibility of the parent)
358 if delta_bytes < 0:
359 chunk = chunk[-delta_bytes:]
360 delta_bytes = 0
361 truncate = not chunk_within_checkpoint_region
362 self.write(chunk, delta_bytes, truncate)
363 self.swap_with_parent()
364
365 def swap_with_parent(self) -> None:
366 with self.lock, blockchains_lock:
367 # do the swap; possibly multiple ones
368 cnt = 0
369 while True:
370 old_parent = self.parent
371 if not self._swap_with_parent():
372 break
373 # make sure we are making progress
374 cnt += 1
375 if cnt > len(blockchains):
376 raise Exception(f'swapping fork with parent too many times: {cnt}')
377 # we might have become the parent of some of our former siblings
378 for old_sibling in old_parent.get_direct_children():
379 if self.check_hash(old_sibling.forkpoint - 1, old_sibling._prev_hash):
380 old_sibling.parent = self
381
382 def _swap_with_parent(self) -> bool:
383 """Check if this chain became stronger than its parent, and swap
384 the underlying files if so. The Blockchain instances will keep
385 'containing' the same headers, but their ids change and so
386 they will be stored in different files."""
387 if self.parent is None:
388 return False
389 if self.parent.get_chainwork() >= self.get_chainwork():
390 return False
391 self.logger.info(f"swapping {self.forkpoint} {self.parent.forkpoint}")
392 parent_branch_size = self.parent.height() - self.forkpoint + 1
393 forkpoint = self.forkpoint # type: Optional[int]
394 parent = self.parent # type: Optional[Blockchain]
395 child_old_id = self.get_id()
396 parent_old_id = parent.get_id()
397 # swap files
398 # child takes parent's name
399 # parent's new name will be something new (not child's old name)
400 self.assert_headers_file_available(self.path())
401 child_old_name = self.path()
402 with open(self.path(), 'rb') as f:
403 my_data = f.read()
404 self.assert_headers_file_available(parent.path())
405 assert forkpoint > parent.forkpoint, (f"forkpoint of parent chain ({parent.forkpoint}) "
406 f"should be at lower height than children's ({forkpoint})")
407 with open(parent.path(), 'rb') as f:
408 f.seek((forkpoint - parent.forkpoint)*HEADER_SIZE)
409 parent_data = f.read(parent_branch_size*HEADER_SIZE)
410 self.write(parent_data, 0)
411 parent.write(my_data, (forkpoint - parent.forkpoint)*HEADER_SIZE)
412 # swap parameters
413 self.parent, parent.parent = parent.parent, self # type: Optional[Blockchain], Optional[Blockchain]
414 self.forkpoint, parent.forkpoint = parent.forkpoint, self.forkpoint
415 self._forkpoint_hash, parent._forkpoint_hash = parent._forkpoint_hash, hash_raw_header(bh2u(parent_data[:HEADER_SIZE]))
416 self._prev_hash, parent._prev_hash = parent._prev_hash, self._prev_hash
417 # parent's new name
418 os.replace(child_old_name, parent.path())
419 self.update_size()
420 parent.update_size()
421 # update pointers
422 blockchains.pop(child_old_id, None)
423 blockchains.pop(parent_old_id, None)
424 blockchains[self.get_id()] = self
425 blockchains[parent.get_id()] = parent
426 return True
427
428 def get_id(self) -> str:
429 return self._forkpoint_hash
430
431 def assert_headers_file_available(self, path):
432 if os.path.exists(path):
433 return
434 elif not os.path.exists(util.get_headers_dir(self.config)):
435 raise FileNotFoundError('Electrum headers_dir does not exist. Was it deleted while running?')
436 else:
437 raise FileNotFoundError('Cannot find headers file but headers_dir is there. Should be at {}'.format(path))
438
439 @with_lock
440 def write(self, data: bytes, offset: int, truncate: bool=True) -> None:
441 filename = self.path()
442 self.assert_headers_file_available(filename)
443 with open(filename, 'rb+') as f:
444 if truncate and offset != self._size * HEADER_SIZE:
445 f.seek(offset)
446 f.truncate()
447 f.seek(offset)
448 f.write(data)
449 f.flush()
450 os.fsync(f.fileno())
451 self.update_size()
452
453 @with_lock
454 def save_header(self, header: dict) -> None:
455 delta = header.get('block_height') - self.forkpoint
456 data = bfh(serialize_header(header))
457 # headers are only _appended_ to the end:
458 assert delta == self.size(), (delta, self.size())
459 assert len(data) == HEADER_SIZE
460 self.write(data, delta*HEADER_SIZE)
461 self.swap_with_parent()
462
463 @with_lock
464 def read_header(self, height: int) -> Optional[dict]:
465 if height < 0:
466 return
467 if height < self.forkpoint:
468 return self.parent.read_header(height)
469 if height > self.height():
470 return
471 delta = height - self.forkpoint
472 name = self.path()
473 self.assert_headers_file_available(name)
474 with open(name, 'rb') as f:
475 f.seek(delta * HEADER_SIZE)
476 h = f.read(HEADER_SIZE)
477 if len(h) < HEADER_SIZE:
478 raise Exception('Expected to read a full header. This was only {} bytes'.format(len(h)))
479 if h == bytes([0])*HEADER_SIZE:
480 return None
481 return deserialize_header(h, height)
482
483 def header_at_tip(self) -> Optional[dict]:
484 """Return latest header."""
485 height = self.height()
486 return self.read_header(height)
487
488 def is_tip_stale(self) -> bool:
489 STALE_DELAY = 8 * 60 * 60 # in seconds
490 header = self.header_at_tip()
491 if not header:
492 return True
493 # note: We check the timestamp only in the latest header.
494 # The Bitcoin consensus has a lot of leeway here:
495 # - needs to be greater than the median of the timestamps of the past 11 blocks, and
496 # - up to at most 2 hours into the future compared to local clock
497 # so there is ~2 hours of leeway in either direction
498 if header['timestamp'] + STALE_DELAY < time.time():
499 return True
500 return False
501
502 def get_hash(self, height: int) -> str:
503 def is_height_checkpoint():
504 within_cp_range = height <= constants.net.max_checkpoint()
505 at_chunk_boundary = (height+1) % 2016 == 0
506 return within_cp_range and at_chunk_boundary
507
508 if height == -1:
509 return '0000000000000000000000000000000000000000000000000000000000000000'
510 elif height == 0:
511 return constants.net.GENESIS
512 elif is_height_checkpoint():
513 index = height // 2016
514 h, t = self.checkpoints[index]
515 return h
516 else:
517 header = self.read_header(height)
518 if header is None:
519 raise MissingHeader(height)
520 return hash_header(header)
521
522 def get_target(self, index: int) -> int:
523 # compute target from chunk x, used in chunk x+1
524 if constants.net.TESTNET:
525 return 0
526 if index == -1:
527 return MAX_TARGET
528 if index < len(self.checkpoints):
529 h, t = self.checkpoints[index]
530 return t
531 # new target
532 first = self.read_header(index * 2016)
533 last = self.read_header(index * 2016 + 2015)
534 if not first or not last:
535 raise MissingHeader()
536 bits = last.get('bits')
537 target = self.bits_to_target(bits)
538 nActualTimespan = last.get('timestamp') - first.get('timestamp')
539 nTargetTimespan = 14 * 24 * 60 * 60
540 nActualTimespan = max(nActualTimespan, nTargetTimespan // 4)
541 nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
542 new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan)
543 # not any target can be represented in 32 bits:
544 new_target = self.bits_to_target(self.target_to_bits(new_target))
545 return new_target
546
547 @classmethod
548 def bits_to_target(cls, bits: int) -> int:
549 bitsN = (bits >> 24) & 0xff
550 if not (0x03 <= bitsN <= 0x1d):
551 raise Exception("First part of bits should be in [0x03, 0x1d]")
552 bitsBase = bits & 0xffffff
553 if not (0x8000 <= bitsBase <= 0x7fffff):
554 raise Exception("Second part of bits should be in [0x8000, 0x7fffff]")
555 return bitsBase << (8 * (bitsN-3))
556
557 @classmethod
558 def target_to_bits(cls, target: int) -> int:
559 c = ("%064x" % target)[2:]
560 while c[:2] == '00' and len(c) > 6:
561 c = c[2:]
562 bitsN, bitsBase = len(c) // 2, int.from_bytes(bfh(c[:6]), byteorder='big')
563 if bitsBase >= 0x800000:
564 bitsN += 1
565 bitsBase >>= 8
566 return bitsN << 24 | bitsBase
567
568 def chainwork_of_header_at_height(self, height: int) -> int:
569 """work done by single header at given height"""
570 chunk_idx = height // 2016 - 1
571 target = self.get_target(chunk_idx)
572 work = ((2 ** 256 - target - 1) // (target + 1)) + 1
573 return work
574
575 @with_lock
576 def get_chainwork(self, height=None) -> int:
577 if height is None:
578 height = max(0, self.height())
579 if constants.net.TESTNET:
580 # On testnet/regtest, difficulty works somewhat different.
581 # It's out of scope to properly implement that.
582 return height
583 last_retarget = height // 2016 * 2016 - 1
584 cached_height = last_retarget
585 while _CHAINWORK_CACHE.get(self.get_hash(cached_height)) is None:
586 if cached_height <= -1:
587 break
588 cached_height -= 2016
589 assert cached_height >= -1, cached_height
590 running_total = _CHAINWORK_CACHE[self.get_hash(cached_height)]
591 while cached_height < last_retarget:
592 cached_height += 2016
593 work_in_single_header = self.chainwork_of_header_at_height(cached_height)
594 work_in_chunk = 2016 * work_in_single_header
595 running_total += work_in_chunk
596 _CHAINWORK_CACHE[self.get_hash(cached_height)] = running_total
597 cached_height += 2016
598 work_in_single_header = self.chainwork_of_header_at_height(cached_height)
599 work_in_last_partial_chunk = (height % 2016 + 1) * work_in_single_header
600 return running_total + work_in_last_partial_chunk
601
602 def can_connect(self, header: dict, check_height: bool=True) -> bool:
603 if header is None:
604 return False
605 height = header['block_height']
606 if check_height and self.height() != height - 1:
607 return False
608 if height == 0:
609 return hash_header(header) == constants.net.GENESIS
610 try:
611 prev_hash = self.get_hash(height - 1)
612 except:
613 return False
614 if prev_hash != header.get('prev_block_hash'):
615 return False
616 try:
617 target = self.get_target(height // 2016 - 1)
618 except MissingHeader:
619 return False
620 try:
621 self.verify_header(header, prev_hash, target)
622 except BaseException as e:
623 return False
624 return True
625
626 def connect_chunk(self, idx: int, hexdata: str) -> bool:
627 assert idx >= 0, idx
628 try:
629 data = bfh(hexdata)
630 self.verify_chunk(idx, data)
631 self.save_chunk(idx, data)
632 return True
633 except BaseException as e:
634 self.logger.info(f'verify_chunk idx {idx} failed: {repr(e)}')
635 return False
636
637 def get_checkpoints(self):
638 # for each chunk, store the hash of the last block and the target after the chunk
639 cp = []
640 n = self.height() // 2016
641 for index in range(n):
642 h = self.get_hash((index+1) * 2016 -1)
643 target = self.get_target(index)
644 cp.append((h, target))
645 return cp
646
647
648 def check_header(header: dict) -> Optional[Blockchain]:
649 """Returns any Blockchain that contains header, or None."""
650 if type(header) is not dict:
651 return None
652 with blockchains_lock: chains = list(blockchains.values())
653 for b in chains:
654 if b.check_header(header):
655 return b
656 return None
657
658
659 def can_connect(header: dict) -> Optional[Blockchain]:
660 """Returns the Blockchain that has a tip that directly links up
661 with header, or None.
662 """
663 with blockchains_lock: chains = list(blockchains.values())
664 for b in chains:
665 if b.can_connect(header):
666 return b
667 return None
668
669
670 def get_chains_that_contain_header(height: int, header_hash: str) -> Sequence[Blockchain]:
671 """Returns a list of Blockchains that contain header, best chain first."""
672 with blockchains_lock: chains = list(blockchains.values())
673 chains = [chain for chain in chains
674 if chain.check_hash(height=height, header_hash=header_hash)]
675 chains = sorted(chains, key=lambda x: x.get_chainwork(), reverse=True)
676 return chains