blockchain.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