From: Forrest Voight Date: Sun, 4 Dec 2011 00:38:28 +0000 (-0500) Subject: moved bitcoin.data.Tracker to util.forest.Tracker X-Git-Tag: 0.8.2~143^2~50 X-Git-Url: https://git.novaco.in/?a=commitdiff_plain;h=3f641f5aebcd78ec924406155b6247103a184325;p=p2pool.git moved bitcoin.data.Tracker to util.forest.Tracker --- diff --git a/p2pool/bitcoin/data.py b/p2pool/bitcoin/data.py index 6b81aac..972c2aa 100644 --- a/p2pool/bitcoin/data.py +++ b/p2pool/bitcoin/data.py @@ -1,13 +1,12 @@ from __future__ import division import hashlib -import itertools import struct from twisted.internet import defer from . import base58, skiplists -from p2pool.util import bases, math, variable, expiring_dict, memoize, dicts +from p2pool.util import bases, math, expiring_dict, memoize, dicts import p2pool class EarlyEnd(Exception): @@ -571,305 +570,3 @@ def script2_to_human(script2, net): return 'Address. Address: %s' % (pubkey_hash_to_address(pubkey_hash, net),) return 'Unknown. Script: %s' % (script2.encode('hex'),) - -# linked list tracker - -class Tracker(object): - def __init__(self): - self.shares = {} # hash -> share - #self.ids = {} # hash -> (id, height) - self.reverse_shares = {} # previous_hash -> set of share_hashes - - self.heads = {} # head hash -> tail_hash - self.tails = {} # tail hash -> set of head hashes - - self.heights = {} # share_hash -> height_to, ref, work_inc - self.reverse_heights = {} # ref -> set of share_hashes - - self.ref_generator = itertools.count() - self.height_refs = {} # ref -> height, share_hash, work_inc - self.reverse_height_refs = {} # share_hash -> ref - - self.get_nth_parent_hash = skiplists.DistanceSkipList(self) - - self.added = variable.Event() - self.removed = variable.Event() - - def add(self, share): - assert not isinstance(share, (int, long, type(None))) - if share.hash in self.shares: - raise ValueError('share already present') - - if share.hash in self.tails: - heads = self.tails.pop(share.hash) - else: - heads = set([share.hash]) - - if share.previous_hash in self.heads: - tail = self.heads.pop(share.previous_hash) - else: - tail = self.get_last(share.previous_hash) - #tail2 = share.previous_hash - #while tail2 in self.shares: - # tail2 = self.shares[tail2].previous_hash - #assert tail == tail2 - - self.shares[share.hash] = share - self.reverse_shares.setdefault(share.previous_hash, set()).add(share.hash) - - self.tails.setdefault(tail, set()).update(heads) - if share.previous_hash in self.tails[tail]: - self.tails[tail].remove(share.previous_hash) - - for head in heads: - self.heads[head] = tail - - self.added.happened(share) - - def test(self): - t = Tracker() - for s in self.shares.itervalues(): - t.add(s) - - assert self.shares == t.shares, (self.shares, t.shares) - assert self.reverse_shares == t.reverse_shares, (self.reverse_shares, t.reverse_shares) - assert self.heads == t.heads, (self.heads, t.heads) - assert self.tails == t.tails, (self.tails, t.tails) - - def remove(self, share_hash): - assert isinstance(share_hash, (int, long, type(None))) - if share_hash not in self.shares: - raise KeyError() - - share = self.shares[share_hash] - del share_hash - - children = self.reverse_shares.get(share.hash, set()) - - # move height refs referencing children down to this, so they can be moved up in one step - if share.previous_hash in self.reverse_height_refs: - if share.previous_hash not in self.tails: - for x in list(self.reverse_heights.get(self.reverse_height_refs.get(share.previous_hash, object()), set())): - self.get_last(x) - for x in list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, object()), set())): - self.get_last(x) - assert share.hash not in self.reverse_height_refs, list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, None), set())) - - if share.hash in self.heads and share.previous_hash in self.tails: - tail = self.heads.pop(share.hash) - self.tails[tail].remove(share.hash) - if not self.tails[share.previous_hash]: - self.tails.pop(share.previous_hash) - elif share.hash in self.heads: - tail = self.heads.pop(share.hash) - self.tails[tail].remove(share.hash) - if self.reverse_shares[share.previous_hash] != set([share.hash]): - pass # has sibling - else: - self.tails[tail].add(share.previous_hash) - self.heads[share.previous_hash] = tail - elif share.previous_hash in self.tails: - heads = self.tails[share.previous_hash] - if len(self.reverse_shares[share.previous_hash]) > 1: - raise NotImplementedError() - else: - del self.tails[share.previous_hash] - for head in heads: - self.heads[head] = share.hash - self.tails[share.hash] = set(heads) - else: - raise NotImplementedError() - - # move ref pointing to this up - if share.previous_hash in self.reverse_height_refs: - assert share.hash not in self.reverse_height_refs, list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, object()), set())) - - ref = self.reverse_height_refs[share.previous_hash] - cur_height, cur_hash, cur_work = self.height_refs[ref] - assert cur_hash == share.previous_hash - self.height_refs[ref] = cur_height - 1, share.hash, cur_work - target_to_average_attempts(share.target) - del self.reverse_height_refs[share.previous_hash] - self.reverse_height_refs[share.hash] = ref - - # delete height entry, and ref if it is empty - if share.hash in self.heights: - _, ref, _ = self.heights.pop(share.hash) - self.reverse_heights[ref].remove(share.hash) - if not self.reverse_heights[ref]: - del self.reverse_heights[ref] - _, ref_hash, _ = self.height_refs.pop(ref) - del self.reverse_height_refs[ref_hash] - - self.shares.pop(share.hash) - self.reverse_shares[share.previous_hash].remove(share.hash) - if not self.reverse_shares[share.previous_hash]: - self.reverse_shares.pop(share.previous_hash) - - #assert self.test() is None - self.removed.happened(share) - - def get_height(self, share_hash): - height, work, last = self.get_height_work_and_last(share_hash) - return height - - def get_work(self, share_hash): - height, work, last = self.get_height_work_and_last(share_hash) - return work - - def get_last(self, share_hash): - height, work, last = self.get_height_work_and_last(share_hash) - return last - - def get_height_and_last(self, share_hash): - height, work, last = self.get_height_work_and_last(share_hash) - return height, last - - def _get_height_jump(self, share_hash): - if share_hash in self.heights: - height_to1, ref, work_inc1 = self.heights[share_hash] - height_to2, share_hash, work_inc2 = self.height_refs[ref] - height_inc = height_to1 + height_to2 - work_inc = work_inc1 + work_inc2 - else: - height_inc, share_hash, work_inc = 1, self.shares[share_hash].previous_hash, target_to_average_attempts(self.shares[share_hash].target) - return height_inc, share_hash, work_inc - - def _set_height_jump(self, share_hash, height_inc, other_share_hash, work_inc): - if other_share_hash not in self.reverse_height_refs: - ref = self.ref_generator.next() - assert ref not in self.height_refs - self.height_refs[ref] = 0, other_share_hash, 0 - self.reverse_height_refs[other_share_hash] = ref - del ref - - ref = self.reverse_height_refs[other_share_hash] - ref_height_to, ref_share_hash, ref_work_inc = self.height_refs[ref] - assert ref_share_hash == other_share_hash - - if share_hash in self.heights: - prev_ref = self.heights[share_hash][1] - self.reverse_heights[prev_ref].remove(share_hash) - if not self.reverse_heights[prev_ref] and prev_ref != ref: - self.reverse_heights.pop(prev_ref) - _, x, _ = self.height_refs.pop(prev_ref) - self.reverse_height_refs.pop(x) - self.heights[share_hash] = height_inc - ref_height_to, ref, work_inc - ref_work_inc - self.reverse_heights.setdefault(ref, set()).add(share_hash) - - def get_height_work_and_last(self, share_hash): - assert isinstance(share_hash, (int, long, type(None))) - orig = share_hash - height = 0 - work = 0 - updates = [] - while share_hash in self.shares: - updates.append((share_hash, height, work)) - height_inc, share_hash, work_inc = self._get_height_jump(share_hash) - height += height_inc - work += work_inc - for update_hash, height_then, work_then in updates: - self._set_height_jump(update_hash, height - height_then, share_hash, work - work_then) - return height, work, share_hash - - def get_chain_known(self, start_hash): - assert isinstance(start_hash, (int, long, type(None))) - ''' - Chain starting with item of hash I{start_hash} of items that this Tracker contains - ''' - item_hash_to_get = start_hash - while True: - if item_hash_to_get not in self.shares: - break - share = self.shares[item_hash_to_get] - assert not isinstance(share, long) - yield share - item_hash_to_get = share.previous_hash - - def get_chain_to_root(self, start_hash, root=None): - assert isinstance(start_hash, (int, long, type(None))) - assert isinstance(root, (int, long, type(None))) - ''' - Chain of hashes starting with share_hash of shares to the root (doesn't include root) - Raises an error if one is missing - ''' - share_hash_to_get = start_hash - while share_hash_to_get != root: - share = self.shares[share_hash_to_get] - yield share - share_hash_to_get = share.previous_hash - - def get_best_hash(self): - ''' - Returns hash of item with the most items in its chain - ''' - if not self.heads: - return None - return max(self.heads, key=self.get_height_and_last) - - def get_highest_height(self): - return max(self.get_height_and_last(head)[0] for head in self.heads) if self.heads else 0 - - def is_child_of(self, share_hash, possible_child_hash): - height, last = self.get_height_and_last(share_hash) - child_height, child_last = self.get_height_and_last(possible_child_hash) - if child_last != last: - return None # not connected, so can't be determined - height_up = child_height - height - return height_up >= 0 and self.get_nth_parent_hash(possible_child_hash, height_up) == share_hash - -class FakeShare(object): - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - -if __name__ == '__main__': - - t = Tracker() - - for i in xrange(10000): - t.add(FakeShare(hash=i, previous_hash=i - 1 if i > 0 else None)) - - #t.remove(99) - - print 'HEADS', t.heads - print 'TAILS', t.tails - - import random - - while False: - print - print '-'*30 - print - t = Tracker() - for i in xrange(random.randrange(100)): - x = random.choice(list(t.shares) + [None]) - print i, '->', x - t.add(FakeShare(i, x)) - while t.shares: - x = random.choice(list(t.shares)) - print 'DEL', x, t.__dict__ - try: - t.remove(x) - except NotImplementedError: - print 'aborted; not implemented' - import time - time.sleep(.1) - print 'HEADS', t.heads - print 'TAILS', t.tails - - #for share_hash, share in sorted(t.shares.iteritems()): - # print share_hash, share.previous_hash, t.heads.get(share_hash), t.tails.get(share_hash) - - #import sys;sys.exit() - - print t.get_nth_parent_hash(9000, 5000) - print t.get_nth_parent_hash(9001, 412) - #print t.get_nth_parent_hash(90, 51) - - for share_hash in sorted(t.shares): - print str(share_hash).rjust(4), - x = t.skips.get(share_hash, None) - if x is not None: - print str(x[0]).rjust(4), - for a in x[1]: - print str(a).rjust(10), - print diff --git a/p2pool/bitcoin/p2p.py b/p2pool/bitcoin/p2p.py index a41caaa..e0aaa56 100644 --- a/p2pool/bitcoin/p2p.py +++ b/p2pool/bitcoin/p2p.py @@ -13,7 +13,7 @@ from twisted.internet import defer, protocol, reactor, task from twisted.python import log from . import data as bitcoin_data -from p2pool.util import variable, datachunker, deferral +from p2pool.util import variable, datachunker, deferral, forest class TooLong(Exception): pass @@ -301,7 +301,7 @@ class HeightTracker(object): def __init__(self, factory, backing): self.factory = factory - self.tracker = bitcoin_data.Tracker() + self.tracker = forest.Tracker() self.backing = backing self.most_recent = None diff --git a/p2pool/bitcoin/skiplists.py b/p2pool/bitcoin/skiplists.py deleted file mode 100644 index b84d253..0000000 --- a/p2pool/bitcoin/skiplists.py +++ /dev/null @@ -1,49 +0,0 @@ -from p2pool.util import skiplist - -class DistanceSkipList(skiplist.SkipList): - def __init__(self, tracker): - skiplist.SkipList.__init__(self) - self.tracker = tracker - - def previous(self, element): - return self.tracker.shares[element].previous_hash - - def get_delta(self, element): - return element, 1, self.tracker.shares[element].previous_hash - - def combine_deltas(self, (from_hash1, dist1, to_hash1), (from_hash2, dist2, to_hash2)): - if to_hash1 != from_hash2: - raise AssertionError() - return from_hash1, dist1 + dist2, to_hash2 - - def initial_solution(self, start, (n,)): - return 0, start - - def apply_delta(self, (dist1, to_hash1), (from_hash2, dist2, to_hash2), (n,)): - if to_hash1 != from_hash2: - raise AssertionError() - return dist1 + dist2, to_hash2 - - def judge(self, (dist, hash), (n,)): - if dist > n: - return 1 - elif dist == n: - return 0 - else: - return -1 - - def finalize(self, (dist, hash)): - return hash - -if __name__ == '__main__': - import random - from p2pool.bitcoin import data - t = data.Tracker() - d = DistanceSkipList(t) - for i in xrange(2000): - t.add(data.FakeShare(hash=i, previous_hash=i - 1 if i > 0 else None)) - for i in xrange(2000): - a = random.randrange(2000) - b = random.randrange(a + 1) - res = d(a, b) - assert res == a - b, (a, b, res) diff --git a/p2pool/data.py b/p2pool/data.py index a018556..05804f5 100644 --- a/p2pool/data.py +++ b/p2pool/data.py @@ -13,7 +13,7 @@ from twisted.python import log import p2pool from p2pool import skiplists from p2pool.bitcoin import data as bitcoin_data, script, networks -from p2pool.util import memoize, expiring_dict, math +from p2pool.util import memoize, expiring_dict, math, forest new_share_data_type = bitcoin_data.ComposedType([ @@ -254,17 +254,17 @@ def new_generate_transaction(tracker, new_share_data, block_target, desired_time ) -class OkayTracker(bitcoin_data.Tracker): +class OkayTracker(forest.Tracker): def __init__(self, net): - bitcoin_data.Tracker.__init__(self) + forest.Tracker.__init__(self) self.net = net - self.verified = bitcoin_data.Tracker() + self.verified = forest.Tracker() self.verified.get_nth_parent_hash = self.get_nth_parent_hash # self is a superset of self.verified self.get_cumulative_weights = skiplists.WeightsSkipList(self) def add(self, share, known_verified=False): - bitcoin_data.Tracker.add(self, share) + forest.Tracker.add(self, share) if known_verified: self.verified.add(share) diff --git a/p2pool/skiplists.py b/p2pool/skiplists.py index b749119..2cfa560 100644 --- a/p2pool/skiplists.py +++ b/p2pool/skiplists.py @@ -88,8 +88,8 @@ class CountsSkipList(skiplist.SkipList): if __name__ == '__main__': import random - from p2pool.bitcoin import data - t = data.Tracker() + from p2pool.util import forest + t = forest.Tracker() d = WeightsSkipList(t) for i in xrange(2000): t.add(data.FakeShare(hash=i, previous_hash=i - 1 if i > 0 else None, new_script=i, target=random.randrange(2**249, 2**250))) diff --git a/p2pool/util/forest.py b/p2pool/util/forest.py new file mode 100644 index 0000000..8682342 --- /dev/null +++ b/p2pool/util/forest.py @@ -0,0 +1,355 @@ +import itertools + +from p2pool.util import skiplist, variable + +from p2pool.bitcoin import data as bitcoin_data + +class DistanceSkipList(skiplist.SkipList): + def __init__(self, tracker): + skiplist.SkipList.__init__(self) + self.tracker = tracker + + def previous(self, element): + return self.tracker.shares[element].previous_hash + + def get_delta(self, element): + return element, 1, self.tracker.shares[element].previous_hash + + def combine_deltas(self, (from_hash1, dist1, to_hash1), (from_hash2, dist2, to_hash2)): + if to_hash1 != from_hash2: + raise AssertionError() + return from_hash1, dist1 + dist2, to_hash2 + + def initial_solution(self, start, (n,)): + return 0, start + + def apply_delta(self, (dist1, to_hash1), (from_hash2, dist2, to_hash2), (n,)): + if to_hash1 != from_hash2: + raise AssertionError() + return dist1 + dist2, to_hash2 + + def judge(self, (dist, hash), (n,)): + if dist > n: + return 1 + elif dist == n: + return 0 + else: + return -1 + + def finalize(self, (dist, hash)): + return hash + +if __name__ == '__main__': + import random + from p2pool.bitcoin import data + t = data.Tracker() + d = DistanceSkipList(t) + for i in xrange(2000): + t.add(data.FakeShare(hash=i, previous_hash=i - 1 if i > 0 else None)) + for i in xrange(2000): + a = random.randrange(2000) + b = random.randrange(a + 1) + res = d(a, b) + assert res == a - b, (a, b, res) + +# linked list tracker + +class Tracker(object): + def __init__(self): + self.shares = {} # hash -> share + #self.ids = {} # hash -> (id, height) + self.reverse_shares = {} # previous_hash -> set of share_hashes + + self.heads = {} # head hash -> tail_hash + self.tails = {} # tail hash -> set of head hashes + + self.heights = {} # share_hash -> height_to, ref, work_inc + self.reverse_heights = {} # ref -> set of share_hashes + + self.ref_generator = itertools.count() + self.height_refs = {} # ref -> height, share_hash, work_inc + self.reverse_height_refs = {} # share_hash -> ref + + self.get_nth_parent_hash = DistanceSkipList(self) + + self.added = variable.Event() + self.removed = variable.Event() + + def add(self, share): + assert not isinstance(share, (int, long, type(None))) + if share.hash in self.shares: + raise ValueError('share already present') + + if share.hash in self.tails: + heads = self.tails.pop(share.hash) + else: + heads = set([share.hash]) + + if share.previous_hash in self.heads: + tail = self.heads.pop(share.previous_hash) + else: + tail = self.get_last(share.previous_hash) + #tail2 = share.previous_hash + #while tail2 in self.shares: + # tail2 = self.shares[tail2].previous_hash + #assert tail == tail2 + + self.shares[share.hash] = share + self.reverse_shares.setdefault(share.previous_hash, set()).add(share.hash) + + self.tails.setdefault(tail, set()).update(heads) + if share.previous_hash in self.tails[tail]: + self.tails[tail].remove(share.previous_hash) + + for head in heads: + self.heads[head] = tail + + self.added.happened(share) + + def test(self): + t = Tracker() + for s in self.shares.itervalues(): + t.add(s) + + assert self.shares == t.shares, (self.shares, t.shares) + assert self.reverse_shares == t.reverse_shares, (self.reverse_shares, t.reverse_shares) + assert self.heads == t.heads, (self.heads, t.heads) + assert self.tails == t.tails, (self.tails, t.tails) + + def remove(self, share_hash): + assert isinstance(share_hash, (int, long, type(None))) + if share_hash not in self.shares: + raise KeyError() + + share = self.shares[share_hash] + del share_hash + + children = self.reverse_shares.get(share.hash, set()) + + # move height refs referencing children down to this, so they can be moved up in one step + if share.previous_hash in self.reverse_height_refs: + if share.previous_hash not in self.tails: + for x in list(self.reverse_heights.get(self.reverse_height_refs.get(share.previous_hash, object()), set())): + self.get_last(x) + for x in list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, object()), set())): + self.get_last(x) + assert share.hash not in self.reverse_height_refs, list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, None), set())) + + if share.hash in self.heads and share.previous_hash in self.tails: + tail = self.heads.pop(share.hash) + self.tails[tail].remove(share.hash) + if not self.tails[share.previous_hash]: + self.tails.pop(share.previous_hash) + elif share.hash in self.heads: + tail = self.heads.pop(share.hash) + self.tails[tail].remove(share.hash) + if self.reverse_shares[share.previous_hash] != set([share.hash]): + pass # has sibling + else: + self.tails[tail].add(share.previous_hash) + self.heads[share.previous_hash] = tail + elif share.previous_hash in self.tails: + heads = self.tails[share.previous_hash] + if len(self.reverse_shares[share.previous_hash]) > 1: + raise NotImplementedError() + else: + del self.tails[share.previous_hash] + for head in heads: + self.heads[head] = share.hash + self.tails[share.hash] = set(heads) + else: + raise NotImplementedError() + + # move ref pointing to this up + if share.previous_hash in self.reverse_height_refs: + assert share.hash not in self.reverse_height_refs, list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, object()), set())) + + ref = self.reverse_height_refs[share.previous_hash] + cur_height, cur_hash, cur_work = self.height_refs[ref] + assert cur_hash == share.previous_hash + self.height_refs[ref] = cur_height - 1, share.hash, cur_work - bitcoin_data.target_to_average_attempts(share.target) + del self.reverse_height_refs[share.previous_hash] + self.reverse_height_refs[share.hash] = ref + + # delete height entry, and ref if it is empty + if share.hash in self.heights: + _, ref, _ = self.heights.pop(share.hash) + self.reverse_heights[ref].remove(share.hash) + if not self.reverse_heights[ref]: + del self.reverse_heights[ref] + _, ref_hash, _ = self.height_refs.pop(ref) + del self.reverse_height_refs[ref_hash] + + self.shares.pop(share.hash) + self.reverse_shares[share.previous_hash].remove(share.hash) + if not self.reverse_shares[share.previous_hash]: + self.reverse_shares.pop(share.previous_hash) + + #assert self.test() is None + self.removed.happened(share) + + def get_height(self, share_hash): + height, work, last = self.get_height_work_and_last(share_hash) + return height + + def get_work(self, share_hash): + height, work, last = self.get_height_work_and_last(share_hash) + return work + + def get_last(self, share_hash): + height, work, last = self.get_height_work_and_last(share_hash) + return last + + def get_height_and_last(self, share_hash): + height, work, last = self.get_height_work_and_last(share_hash) + return height, last + + def _get_height_jump(self, share_hash): + if share_hash in self.heights: + height_to1, ref, work_inc1 = self.heights[share_hash] + height_to2, share_hash, work_inc2 = self.height_refs[ref] + height_inc = height_to1 + height_to2 + work_inc = work_inc1 + work_inc2 + else: + height_inc, share_hash, work_inc = 1, self.shares[share_hash].previous_hash, bitcoin_data.target_to_average_attempts(self.shares[share_hash].target) + return height_inc, share_hash, work_inc + + def _set_height_jump(self, share_hash, height_inc, other_share_hash, work_inc): + if other_share_hash not in self.reverse_height_refs: + ref = self.ref_generator.next() + assert ref not in self.height_refs + self.height_refs[ref] = 0, other_share_hash, 0 + self.reverse_height_refs[other_share_hash] = ref + del ref + + ref = self.reverse_height_refs[other_share_hash] + ref_height_to, ref_share_hash, ref_work_inc = self.height_refs[ref] + assert ref_share_hash == other_share_hash + + if share_hash in self.heights: + prev_ref = self.heights[share_hash][1] + self.reverse_heights[prev_ref].remove(share_hash) + if not self.reverse_heights[prev_ref] and prev_ref != ref: + self.reverse_heights.pop(prev_ref) + _, x, _ = self.height_refs.pop(prev_ref) + self.reverse_height_refs.pop(x) + self.heights[share_hash] = height_inc - ref_height_to, ref, work_inc - ref_work_inc + self.reverse_heights.setdefault(ref, set()).add(share_hash) + + def get_height_work_and_last(self, share_hash): + assert isinstance(share_hash, (int, long, type(None))) + orig = share_hash + height = 0 + work = 0 + updates = [] + while share_hash in self.shares: + updates.append((share_hash, height, work)) + height_inc, share_hash, work_inc = self._get_height_jump(share_hash) + height += height_inc + work += work_inc + for update_hash, height_then, work_then in updates: + self._set_height_jump(update_hash, height - height_then, share_hash, work - work_then) + return height, work, share_hash + + def get_chain_known(self, start_hash): + assert isinstance(start_hash, (int, long, type(None))) + ''' + Chain starting with item of hash I{start_hash} of items that this Tracker contains + ''' + item_hash_to_get = start_hash + while True: + if item_hash_to_get not in self.shares: + break + share = self.shares[item_hash_to_get] + assert not isinstance(share, long) + yield share + item_hash_to_get = share.previous_hash + + def get_chain_to_root(self, start_hash, root=None): + assert isinstance(start_hash, (int, long, type(None))) + assert isinstance(root, (int, long, type(None))) + ''' + Chain of hashes starting with share_hash of shares to the root (doesn't include root) + Raises an error if one is missing + ''' + share_hash_to_get = start_hash + while share_hash_to_get != root: + share = self.shares[share_hash_to_get] + yield share + share_hash_to_get = share.previous_hash + + def get_best_hash(self): + ''' + Returns hash of item with the most items in its chain + ''' + if not self.heads: + return None + return max(self.heads, key=self.get_height_and_last) + + def get_highest_height(self): + return max(self.get_height_and_last(head)[0] for head in self.heads) if self.heads else 0 + + def is_child_of(self, share_hash, possible_child_hash): + height, last = self.get_height_and_last(share_hash) + child_height, child_last = self.get_height_and_last(possible_child_hash) + if child_last != last: + return None # not connected, so can't be determined + height_up = child_height - height + return height_up >= 0 and self.get_nth_parent_hash(possible_child_hash, height_up) == share_hash + +class FakeShare(object): + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + +if __name__ == '__main__': + + t = Tracker() + + for i in xrange(10000): + t.add(FakeShare(hash=i, previous_hash=i - 1 if i > 0 else None)) + + #t.remove(99) + + print 'HEADS', t.heads + print 'TAILS', t.tails + + import random + + while False: + print + print '-'*30 + print + t = Tracker() + for i in xrange(random.randrange(100)): + x = random.choice(list(t.shares) + [None]) + print i, '->', x + t.add(FakeShare(i, x)) + while t.shares: + x = random.choice(list(t.shares)) + print 'DEL', x, t.__dict__ + try: + t.remove(x) + except NotImplementedError: + print 'aborted; not implemented' + import time + time.sleep(.1) + print 'HEADS', t.heads + print 'TAILS', t.tails + + #for share_hash, share in sorted(t.shares.iteritems()): + # print share_hash, share.previous_hash, t.heads.get(share_hash), t.tails.get(share_hash) + + #import sys;sys.exit() + + print t.get_nth_parent_hash(9000, 5000) + print t.get_nth_parent_hash(9001, 412) + #print t.get_nth_parent_hash(90, 51) + + for share_hash in sorted(t.shares): + print str(share_hash).rjust(4), + x = t.skips.get(share_hash, None) + if x is not None: + print str(x[0]).rjust(4), + for a in x[1]: + print str(a).rjust(10), + print