def lock_to_index(self, index): old_length = len(self._locked_chain) index -= old_length longest_chain = self._longest_local_block_chain() if index < 1: return excluded = set() for idx in range(index): the_hash = longest_chain[-idx-1] parent_hash = self.parent_hash if idx <= 0 else self._longest_chain_cache[-idx] weight = self.weight_lookup.get(the_hash) item = (the_hash, parent_hash, weight) self._locked_chain.append(item) excluded.add(the_hash) if self.did_lock_to_index_f: self.did_lock_to_index_f(self._locked_chain[old_length:old_length+index], old_length) old_chain_finder = self.chain_finder self.chain_finder = ChainFinder() self._longest_chain_cache = None def iterate(): for tree in old_chain_finder.trees_from_bottom.values(): for c in tree: if c in excluded: break excluded.add(c) yield (c, old_chain_finder.parent_lookup[c]) self.chain_finder.load_nodes(iterate()) self.parent_hash = the_hash
def test_find_ancestral_path(): ITEMS = [BHO(i) for i in range(5)] B201 = BHO(201, 2, 110) B202, B203, B204 = [BHO(i) for i in range(202, 205)] cf = ChainFinder() items = ITEMS + [B202, B203, B204] load_items(cf, items) load_items(cf, [B201]) old_chain_endpoint, new_chain_endpoint = 4, 204 old_subpath, new_subpath = cf.find_ancestral_path(old_chain_endpoint, new_chain_endpoint) assert old_subpath == [4, 3, 2] assert new_subpath == [204, 203, 202, 201, 2]
def __init__(self, parent_hash=ZERO_HASH, did_lock_to_index_f=None): self.parent_hash = parent_hash self.hash_to_index_lookup = {} self.weight_lookup = {} self.chain_finder = ChainFinder() self.change_callbacks = weakref.WeakSet() self._longest_chain_cache = None self.did_lock_to_index_f = did_lock_to_index_f self._locked_chain = []
class BlockChain: def __init__(self, parent_hash=ZERO_HASH, did_lock_to_index_f=None): self.parent_hash = parent_hash self.hash_to_index_lookup = {} self.weight_lookup = {} self.chain_finder = ChainFinder() self.change_callbacks = weakref.WeakSet() self._longest_chain_cache = None self.did_lock_to_index_f = did_lock_to_index_f self._locked_chain = [] def is_hash_known(self, the_hash): return the_hash in self.hash_to_index_lookup def length(self): return len(self._longest_local_block_chain()) + len(self._locked_chain) def locked_length(self): return len(self._locked_chain) def tuple_for_index(self, index): if index < 0: index = self.length() + index l = len(self._locked_chain) if index < l: return self._locked_chain[index] index -= l longest_chain = self._longest_local_block_chain() the_hash = longest_chain[-index-1] parent_hash = self.parent_hash if index <= 0 else self._longest_chain_cache[-index] weight = self.weight_lookup.get(the_hash) return (the_hash, parent_hash, weight) def last_block_hash(self): if self.length() == 0: return self.parent_hash return self.hash_for_index(-1) def hash_for_index(self, index): return self.tuple_for_index(index)[0] def index_for_hash(self, the_hash): return self.hash_to_index_lookup.get(the_hash) def add_change_callback(self, callback): self.change_callbacks.add(callback) def lock_to_index(self, index): old_length = len(self._locked_chain) index -= old_length longest_chain = self._longest_local_block_chain() if index < 1: return excluded = set() for idx in range(index): the_hash = longest_chain[-idx-1] parent_hash = self.parent_hash if idx <= 0 else self._longest_chain_cache[-idx] weight = self.weight_lookup.get(the_hash) item = (the_hash, parent_hash, weight) self._locked_chain.append(item) excluded.add(the_hash) if self.did_lock_to_index_f: self.did_lock_to_index_f(self._locked_chain[old_length:old_length+index], old_length) old_chain_finder = self.chain_finder self.chain_finder = ChainFinder() self._longest_chain_cache = None def iterate(): for tree in old_chain_finder.trees_from_bottom.values(): for c in tree: if c in excluded: break excluded.add(c) yield (c, old_chain_finder.parent_lookup[c]) self.chain_finder.load_nodes(iterate()) self.parent_hash = the_hash def _longest_local_block_chain(self): if self._longest_chain_cache is None: max_weight = 0 longest = [] for chain in self.chain_finder.all_chains_ending_at(self.parent_hash): weight = sum(self.weight_lookup.get(h, 0) for h in chain) if weight > max_weight: longest = chain max_weight = weight self._longest_chain_cache = longest[:-1] return self._longest_chain_cache def add_headers(self, header_iter): def hash_parent_weight_tuples(): for h in header_iter: yield h.hash(), h.previous_block_hash, h.difficulty return self.add_nodes(hash_parent_weight_tuples()) def add_nodes(self, hash_parent_weight_tuples): def iterate(): for h, p, w in hash_parent_weight_tuples: self.weight_lookup[h] = w yield h, p old_longest_chain = self._longest_local_block_chain() self.chain_finder.load_nodes(iterate()) self._longest_chain_cache = None new_longest_chain = self._longest_local_block_chain() if old_longest_chain and new_longest_chain: old_path, new_path = self.chain_finder.find_ancestral_path( old_longest_chain[0], new_longest_chain[0] ) old_path = old_path[:-1] new_path = new_path[:-1] else: old_path = old_longest_chain new_path = new_longest_chain if old_path: logging.debug("old_path is %s-%s", old_path[0], old_path[-1]) if new_path: logging.debug("new_path is %s-%s", new_path[0], new_path[-1]) logging.debug("block chain now has %d elements", self.length()) # return a list of operations: # ("add"/"remove", the_hash, the_index) ops = [] size = len(old_longest_chain) + len(self._locked_chain) for idx, h in enumerate(old_path): op = ("remove", h, size-idx-1) ops.append(op) del self.hash_to_index_lookup[size-idx-1] size = len(new_longest_chain) + len(self._locked_chain) for idx, h in reversed(list(enumerate(new_path))): op = ("add", h, size-idx-1) ops.append(op) self.hash_to_index_lookup[size-idx-1] = h for callback in self.change_callbacks: callback(self, ops) return ops
def test_large(): ITEMS = [BHO(i) for i in range(10000)] cf = ChainFinder() load_items(cf, ITEMS) old_subpath, new_subpath = cf.find_ancestral_path(5000, 9000)
class BlockChain: def __init__(self, parent_hash=ZERO_HASH, did_lock_to_index_f=None): self.parent_hash = parent_hash self.hash_to_index_lookup = {} self.weight_lookup = {} self.chain_finder = ChainFinder() self.change_queues = set() #weakref.WeakSet() self._longest_chain_cache = None self.did_lock_to_index_f = did_lock_to_index_f self._locked_chain = [] def is_hash_known(self, the_hash): return the_hash in self.hash_to_index_lookup def length(self): return len(self._longest_local_block_chain()) + len(self._locked_chain) def locked_length(self): return len(self._locked_chain) def tuple_for_index(self, index): if index < 0: index = self.length() + index l = len(self._locked_chain) if index < l: return self._locked_chain[index] index -= l longest_chain = self._longest_local_block_chain() the_hash = longest_chain[-index - 1] parent_hash = self.parent_hash if index <= 0 else self._longest_chain_cache[ -index] weight = self.weight_lookup.get(the_hash) return (the_hash, parent_hash, weight) def last_block_hash(self): if self.length() == 0: return self.parent_hash return self.hash_for_index(-1) def hash_for_index(self, index): return self.tuple_for_index(index)[0] def index_for_hash(self, the_hash): return self.hash_to_index_lookup.get(the_hash) def new_change_q(self): q = Queue() self.change_queues.add(q) return q def lock_to_index(self, index): old_length = len(self._locked_chain) index -= old_length longest_chain = self._longest_local_block_chain() if index < 1: return excluded = set() for idx in range(index): the_hash = longest_chain[-idx - 1] parent_hash = self.parent_hash if idx <= 0 else self._longest_chain_cache[ -idx] weight = self.weight_lookup.get(the_hash) item = (the_hash, parent_hash, weight) self._locked_chain.append(item) excluded.add(the_hash) if self.did_lock_to_index_f: self.did_lock_to_index_f( self._locked_chain[old_length:old_length + index], old_length) old_chain_finder = self.chain_finder self.chain_finder = ChainFinder() self._longest_chain_cache = None def iterate(): for tree in old_chain_finder.trees_from_bottom.values(): for c in tree: if c in excluded: break excluded.add(c) yield (c, old_chain_finder.parent_lookup[c]) self.chain_finder.load_nodes(iterate()) self.parent_hash = the_hash def _longest_local_block_chain(self): if self._longest_chain_cache is None: max_weight = 0 longest = [] for chain in self.chain_finder.all_chains_ending_at( self.parent_hash): weight = sum(self.weight_lookup.get(h, 0) for h in chain) if weight > max_weight: longest = chain max_weight = weight self._longest_chain_cache = longest[:-1] return self._longest_chain_cache def add_headers(self, header_iter): def hash_parent_weight_tuples(): for h in header_iter: yield h.hash(), h.previous_block_hash, h.difficulty return self.add_nodes(hash_parent_weight_tuples()) def add_nodes(self, hash_parent_weight_tuples): def iterate(): for h, p, w in hash_parent_weight_tuples: self.weight_lookup[h] = w yield h, p old_longest_chain = self._longest_local_block_chain() self.chain_finder.load_nodes(iterate()) self._longest_chain_cache = None new_longest_chain = self._longest_local_block_chain() if old_longest_chain and new_longest_chain: old_path, new_path = self.chain_finder.find_ancestral_path( old_longest_chain[0], new_longest_chain[0]) old_path = old_path[:-1] new_path = new_path[:-1] else: old_path = old_longest_chain new_path = new_longest_chain if old_path: logging.debug("old_path is %s-%s", old_path[0], old_path[-1]) if new_path: logging.debug("new_path is %s-%s", new_path[0], new_path[-1]) logging.debug("block chain now has %d elements", self.length()) # return a list of operations: # ("add"/"remove", the_hash, the_index) ops = [] size = len(old_longest_chain) + len(self._locked_chain) for idx, h in enumerate(old_path): op = ("remove", h, size - idx - 1) ops.append(op) del self.hash_to_index_lookup[size - idx - 1] size = len(new_longest_chain) + len(self._locked_chain) for idx, h in reversed(list(enumerate(new_path))): op = ("add", h, size - idx - 1) ops.append(op) self.hash_to_index_lookup[size - idx - 1] = h for q in self.change_queues: _update_q(q, ops) return ops