electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

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