renamed PossiblyNone to PossiblyNoneType
[p2pool.git] / p2pool / bitcoin / data.py
1 from __future__ import division
2
3 import hashlib
4 import itertools
5 import struct
6
7 from twisted.internet import defer
8
9 from . import base58, skiplists
10 from p2pool.util import bases, math, variable
11 import p2pool
12
13 class EarlyEnd(Exception):
14     pass
15
16 class LateEnd(Exception):
17     pass
18
19 def read((data, pos), length):
20     data2 = data[pos:pos + length]
21     if len(data2) != length:
22         raise EarlyEnd()
23     return data2, (data, pos + length)
24
25 def size((data, pos)):
26     return len(data) - pos
27
28 class Type(object):
29     # the same data can have only one unpacked representation, but multiple packed binary representations
30     
31     #def __hash__(self):
32     #    return hash(tuple(self.__dict__.items()))
33     
34     #def __eq__(self, other):
35     #    if not isinstance(other, Type):
36     #        raise NotImplementedError()
37     #    return self.__dict__ == other.__dict__
38     
39     def _unpack(self, data):
40         obj, (data2, pos) = self.read((data, 0))
41         
42         assert data2 is data
43         
44         if pos != len(data):
45             raise LateEnd()
46         
47         return obj
48     
49     def _pack(self, obj):
50         f = self.write(None, obj)
51         
52         res = []
53         while f is not None:
54             res.append(f[1])
55             f = f[0]
56         res.reverse()
57         return ''.join(res)
58     
59     
60     def unpack(self, data):
61         obj = self._unpack(data)
62         
63         if p2pool.DEBUG:
64             data2 = self._pack(obj)
65             if data2 != data:
66                 if self._unpack(data2) != obj:
67                     raise AssertionError()
68         
69         return obj
70     
71     def pack(self, obj):
72         data = self._pack(obj)
73         
74         if p2pool.DEBUG:
75             if self._unpack(data) != obj:
76                 raise AssertionError()
77         
78         return data
79     
80     
81     def pack_base58(self, obj):
82         return base58.base58_encode(self.pack(obj))
83     
84     def unpack_base58(self, base58_data):
85         return self.unpack(base58.base58_decode(base58_data))
86     
87     
88     def hash160(self, obj):
89         return ShortHashType().unpack(hashlib.new('ripemd160', hashlib.sha256(self.pack(obj)).digest()).digest())
90     
91     def hash256(self, obj):
92         return HashType().unpack(hashlib.sha256(hashlib.sha256(self.pack(obj)).digest()).digest())
93
94 class VarIntType(Type):
95     # redundancy doesn't matter here because bitcoin and p2pool both reencode before hashing
96     def read(self, file):
97         data, file = read(file, 1)
98         first = ord(data)
99         if first < 0xfd:
100             return first, file
101         elif first == 0xfd:
102             desc, length = '<H', 2
103         elif first == 0xfe:
104             desc, length = '<I', 4
105         elif first == 0xff:
106             desc, length = '<Q', 8
107         else:
108             raise AssertionError()
109         data, file = read(file, length)
110         return struct.unpack(desc, data)[0], file
111     
112     def write(self, file, item):
113         if item < 0xfd:
114             file = file, struct.pack('<B', item)
115         elif item <= 0xffff:
116             file = file, struct.pack('<BH', 0xfd, item)
117         elif item <= 0xffffffff:
118             file = file, struct.pack('<BI', 0xfe, item)
119         elif item <= 0xffffffffffffffff:
120             file = file, struct.pack('<BQ', 0xff, item)
121         else:
122             raise ValueError('int too large for varint')
123         return file
124
125 class VarStrType(Type):
126     _inner_size = VarIntType()
127     
128     def read(self, file):
129         length, file = self._inner_size.read(file)
130         return read(file, length)
131     
132     def write(self, file, item):
133         return self._inner_size.write(file, len(item)), item
134
135 class FixedStrType(Type):
136     def __init__(self, length):
137         self.length = length
138     
139     def read(self, file):
140         return read(file, self.length)
141     
142     def write(self, file, item):
143         if len(item) != self.length:
144             raise ValueError('incorrect length item!')
145         return file, item
146
147 class EnumType(Type):
148     def __init__(self, inner, values):
149         self.inner = inner
150         self.values = values
151         
152         self.keys = {}
153         for k, v in values.iteritems():
154             if v in self.keys:
155                 raise ValueError('duplicate value in values')
156             self.keys[v] = k
157     
158     def read(self, file):
159         data, file = self.inner.read(file)
160         if data not in self.keys:
161             raise ValueError('enum data (%r) not in values (%r)' % (data, self.values))
162         return self.keys[data], file
163     
164     def write(self, file, item):
165         if item not in self.values:
166             raise ValueError('enum item (%r) not in values (%r)' % (item, self.values))
167         return self.inner.write(file, self.values[item])
168
169 class HashType(Type):
170     def read(self, file):
171         data, file = read(file, 256//8)
172         return int(data[::-1].encode('hex'), 16), file
173     
174     def write(self, file, item):
175         if not 0 <= item < 2**256:
176             raise ValueError('invalid hash value - %r' % (item,))
177         if item != 0 and item < 2**160:
178             print 'Very low hash value - maybe you meant to use ShortHashType? %x' % (item,)
179         return file, ('%064x' % (item,)).decode('hex')[::-1]
180
181 class ShortHashType(Type):
182     def read(self, file):
183         data, file = read(file, 160//8)
184         return int(data[::-1].encode('hex'), 16), file
185     
186     def write(self, file, item):
187         if not 0 <= item < 2**160:
188             raise ValueError('invalid hash value - %r' % (item,))
189         return file, ('%040x' % (item,)).decode('hex')[::-1]
190
191 class ListType(Type):
192     _inner_size = VarIntType()
193     
194     def __init__(self, type):
195         self.type = type
196     
197     def read(self, file):
198         length, file = self._inner_size.read(file)
199         res = []
200         for i in xrange(length):
201             item, file = self.type.read(file)
202             res.append(item)
203         return res, file
204     
205     def write(self, file, item):
206         file = self._inner_size.write(file, len(item))
207         for subitem in item:
208             file = self.type.write(file, subitem)
209         return file
210
211 class StructType(Type):
212     def __init__(self, desc):
213         self.desc = desc
214         self.length = struct.calcsize(self.desc)
215     
216     def read(self, file):
217         data, file = read(file, self.length)
218         res, = struct.unpack(self.desc, data)
219         return res, file
220     
221     def write(self, file, item):
222         data = struct.pack(self.desc, item)
223         if struct.unpack(self.desc, data)[0] != item:
224             # special test because struct doesn't error on some overflows
225             raise ValueError('''item didn't survive pack cycle (%r)''' % (item,))
226         return file, data
227
228 class IPV6AddressType(Type):
229     def read(self, file):
230         data, file = read(file, 16)
231         if data[:12] != '00000000000000000000ffff'.decode('hex'):
232             raise ValueError('ipv6 addresses not supported yet')
233         return '.'.join(str(ord(x)) for x in data[12:]), file
234     
235     def write(self, file, item):
236         bits = map(int, item.split('.'))
237         if len(bits) != 4:
238             raise ValueError('invalid address: %r' % (bits,))
239         data = '00000000000000000000ffff'.decode('hex') + ''.join(chr(x) for x in bits)
240         assert len(data) == 16, len(data)
241         return file, data
242
243 _record_types = {}
244
245 def get_record(fields):
246     fields = tuple(sorted(fields))
247     if 'keys' in fields:
248         raise ValueError()
249     if fields not in _record_types:
250         class _Record(object):
251             __slots__ = fields
252             def __repr__(self):
253                 return repr(dict(self))
254             def __getitem__(self, key):
255                 return getattr(self, key)
256             def __setitem__(self, key, value):
257                 setattr(self, key, value)
258             #def __iter__(self):
259             #    for field in self.__slots__:
260             #        yield field, getattr(self, field)
261             def keys(self):
262                 return self.__slots__
263             def __eq__(self, other):
264                 if isinstance(other, dict):
265                     return dict(self) == other
266                 elif isinstance(other, _Record):
267                     return all(self[k] == other[k] for k in self.keys())
268                 raise TypeError()
269             def __ne__(self, other):
270                 return not (self == other)
271         _record_types[fields] = _Record
272     return _record_types[fields]()
273
274 class ComposedType(Type):
275     def __init__(self, fields):
276         self.fields = fields
277     
278     def read(self, file):
279         item = get_record(k for k, v in self.fields)
280         for key, type_ in self.fields:
281             item[key], file = type_.read(file)
282         return item, file
283     
284     def write(self, file, item):
285         for key, type_ in self.fields:
286             file = type_.write(file, item[key])
287         return file
288
289 class ChecksummedType(Type):
290     def __init__(self, inner):
291         self.inner = inner
292     
293     def read(self, file):
294         obj, file = self.inner.read(file)
295         data = self.inner.pack(obj)
296         
297         checksum, file = read(file, 4)
298         if checksum != hashlib.sha256(hashlib.sha256(data).digest()).digest()[:4]:
299             raise ValueError('invalid checksum')
300         
301         return obj, file
302     
303     def write(self, file, item):
304         data = self.inner.pack(item)
305         return (file, data), hashlib.sha256(hashlib.sha256(data).digest()).digest()[:4]
306
307 class FloatingInteger(object):
308     __slots__ = ['_bits']
309     
310     @classmethod
311     def from_target_upper_bound(cls, target):
312         n = bases.natural_to_string(target)
313         if n and ord(n[0]) >= 128:
314             n = '\x00' + n
315         bits2 = (chr(len(n)) + (n + 3*chr(0))[:3])[::-1]
316         bits = struct.unpack('<I', bits2)[0]
317         return cls(bits)
318     
319     def __init__(self, bits):
320         self._bits = bits
321     
322     @property
323     def _value(self):
324         return math.shift_left(self._bits & 0x00ffffff, 8 * ((self._bits >> 24) - 3))
325     
326     def __hash__(self):
327         return hash(self._value)
328     
329     def __cmp__(self, other):
330         if isinstance(other, FloatingInteger):
331             return cmp(self._value, other._value)
332         elif isinstance(other, (int, long)):
333             return cmp(self._value, other)
334         else:
335             raise NotImplementedError()
336     
337     def __int__(self):
338         return self._value
339     
340     def __repr__(self):
341         return 'FloatingInteger(bits=%s (%x))' % (hex(self._bits), self)
342     
343     def __add__(self, other):
344         if isinstance(other, (int, long)):
345             return self._value + other
346         raise NotImplementedError()
347     __radd__ = __add__
348     def __mul__(self, other):
349         if isinstance(other, (int, long)):
350             return self._value * other
351         raise NotImplementedError()
352     __rmul__ = __mul__
353     def __truediv__(self, other):
354         if isinstance(other, (int, long)):
355             return self._value / other
356         raise NotImplementedError()
357     def __floordiv__(self, other):
358         if isinstance(other, (int, long)):
359             return self._value // other
360         raise NotImplementedError()
361     __div__ = __truediv__
362     def __rtruediv__(self, other):
363         if isinstance(other, (int, long)):
364             return other / self._value
365         raise NotImplementedError()
366     def __rfloordiv__(self, other):
367         if isinstance(other, (int, long)):
368             return other // self._value
369         raise NotImplementedError()
370     __rdiv__ = __rtruediv__
371
372 class FloatingIntegerType(Type):
373     _inner = StructType('<I')
374     
375     def read(self, file):
376         bits, file = self._inner.read(file)
377         return FloatingInteger(bits), file
378     
379     def write(self, file, item):
380         return self._inner.write(file, item._bits)
381
382 class PossiblyNoneType(Type):
383     def __init__(self, none_value, inner):
384         self.none_value = none_value
385         self.inner = inner
386     
387     def read(self, file):
388         value, file = self.inner.read(file)
389         return None if value == self.none_value else value, file
390     
391     def write(self, file, item):
392         if item == self.none_value:
393             raise ValueError('none_value used')
394         return self.inner.write(file, self.none_value if item is None else item)
395
396 address_type = ComposedType([
397     ('services', StructType('<Q')),
398     ('address', IPV6AddressType()),
399     ('port', StructType('>H')),
400 ])
401
402 tx_type = ComposedType([
403     ('version', StructType('<I')),
404     ('tx_ins', ListType(ComposedType([
405         ('previous_output', PossiblyNoneType(dict(hash=0, index=2**32 - 1), ComposedType([
406             ('hash', HashType()),
407             ('index', StructType('<I')),
408         ]))),
409         ('script', VarStrType()),
410         ('sequence', PossiblyNoneType(2**32 - 1, StructType('<I'))),
411     ]))),
412     ('tx_outs', ListType(ComposedType([
413         ('value', StructType('<Q')),
414         ('script', VarStrType()),
415     ]))),
416     ('lock_time', StructType('<I')),
417 ])
418
419 block_header_type = ComposedType([
420     ('version', StructType('<I')),
421     ('previous_block', PossiblyNoneType(0, HashType())),
422     ('merkle_root', HashType()),
423     ('timestamp', StructType('<I')),
424     ('target', FloatingIntegerType()),
425     ('nonce', StructType('<I')),
426 ])
427
428 block_type = ComposedType([
429     ('header', block_header_type),
430     ('txs', ListType(tx_type)),
431 ])
432
433
434 merkle_record_type = ComposedType([
435     ('left', HashType()),
436     ('right', HashType()),
437 ])
438
439 def merkle_hash(tx_list):
440     if not tx_list:
441         return 0
442     hash_list = map(tx_type.hash256, tx_list)
443     while len(hash_list) > 1:
444         hash_list = [merkle_record_type.hash256(dict(left=left, right=left if right is None else right))
445             for left, right in zip(hash_list[::2], hash_list[1::2] + [None])]
446     return hash_list[0]
447
448 def target_to_average_attempts(target):
449     return 2**256//(target + 1)
450
451 # tx
452
453 def tx_get_sigop_count(tx):
454     return sum(script.get_sigop_count(txin['script']) for txin in tx['tx_ins']) + sum(script.get_sigop_count(txout['script']) for txout in tx['tx_outs'])
455
456 # human addresses
457
458 human_address_type = ChecksummedType(ComposedType([
459     ('version', StructType('<B')),
460     ('pubkey_hash', ShortHashType()),
461 ]))
462
463 pubkey_type = FixedStrType(65)
464
465 def pubkey_hash_to_address(pubkey_hash, net):
466     return human_address_type.pack_base58(dict(version=net.BITCOIN_ADDRESS_VERSION, pubkey_hash=pubkey_hash))
467
468 def pubkey_to_address(pubkey, net):
469     return pubkey_hash_to_address(pubkey_type.hash160(pubkey), net)
470
471 def address_to_pubkey_hash(address, net):
472     x = human_address_type.unpack_base58(address)
473     if x['version'] != net.BITCOIN_ADDRESS_VERSION:
474         raise ValueError('address not for this net!')
475     return x['pubkey_hash']
476
477 # transactions
478
479 def pubkey_to_script2(pubkey):
480     return ('\x41' + pubkey_type.pack(pubkey)) + '\xac'
481
482 def pubkey_hash_to_script2(pubkey_hash):
483     return '\x76\xa9' + ('\x14' + ShortHashType().pack(pubkey_hash)) + '\x88\xac'
484
485 def script2_to_human(script2, net):
486     try:
487         pubkey = script2[1:-1]
488         script2_test = pubkey_to_script2(pubkey)
489     except:
490         pass
491     else:
492         if script2_test == script2:
493             return 'Pubkey. Address: %s' % (pubkey_to_address(pubkey, net),)
494     
495     try:
496         pubkey_hash = ShortHashType().unpack(script2[3:-2])
497         script2_test2 = pubkey_hash_to_script2(pubkey_hash)
498     except:
499         pass
500     else:
501         if script2_test2 == script2:
502             return 'Address. Address: %s' % (pubkey_hash_to_address(pubkey_hash, net),)
503     
504     return 'Unknown. Script: %s'  % (script2.encode('hex'),)
505
506 # linked list tracker
507
508 class Tracker(object):
509     def __init__(self):
510         self.shares = {} # hash -> share
511         #self.ids = {} # hash -> (id, height)
512         self.reverse_shares = {} # previous_hash -> set of share_hashes
513         
514         self.heads = {} # head hash -> tail_hash
515         self.tails = {} # tail hash -> set of head hashes
516         
517         self.heights = {} # share_hash -> height_to, ref, work_inc
518         self.reverse_heights = {} # ref -> set of share_hashes
519         
520         self.ref_generator = itertools.count()
521         self.height_refs = {} # ref -> height, share_hash, work_inc
522         self.reverse_height_refs = {} # share_hash -> ref
523         
524         self.get_nth_parent_hash = skiplists.DistanceSkipList(self)
525         
526         self.added = variable.Event()
527         self.removed = variable.Event()
528     
529     def add(self, share):
530         assert not isinstance(share, (int, long, type(None)))
531         if share.hash in self.shares:
532             raise ValueError('share already present')
533         
534         if share.hash in self.tails:
535             heads = self.tails.pop(share.hash)
536         else:
537             heads = set([share.hash])
538         
539         if share.previous_hash in self.heads:
540             tail = self.heads.pop(share.previous_hash)
541         else:
542             tail = self.get_last(share.previous_hash)
543             #tail2 = share.previous_hash
544             #while tail2 in self.shares:
545             #    tail2 = self.shares[tail2].previous_hash
546             #assert tail == tail2
547         
548         self.shares[share.hash] = share
549         self.reverse_shares.setdefault(share.previous_hash, set()).add(share.hash)
550         
551         self.tails.setdefault(tail, set()).update(heads)
552         if share.previous_hash in self.tails[tail]:
553             self.tails[tail].remove(share.previous_hash)
554         
555         for head in heads:
556             self.heads[head] = tail
557         
558         self.added.happened(share)
559     
560     def test(self):
561         t = Tracker()
562         for s in self.shares.itervalues():
563             t.add(s)
564         
565         assert self.shares == t.shares, (self.shares, t.shares)
566         assert self.reverse_shares == t.reverse_shares, (self.reverse_shares, t.reverse_shares)
567         assert self.heads == t.heads, (self.heads, t.heads)
568         assert self.tails == t.tails, (self.tails, t.tails)
569     
570     def remove(self, share_hash):
571         assert isinstance(share_hash, (int, long, type(None)))
572         if share_hash not in self.shares:
573             raise KeyError()
574         
575         share = self.shares[share_hash]
576         del share_hash
577         
578         children = self.reverse_shares.get(share.hash, set())
579         
580         # move height refs referencing children down to this, so they can be moved up in one step
581         if share.previous_hash in self.reverse_height_refs:
582             for x in list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, object()), set())):
583                 self.get_last(x)
584             assert share.hash not in self.reverse_height_refs, list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, None), set()))
585         
586         if share.hash in self.heads and share.previous_hash in self.tails:
587             tail = self.heads.pop(share.hash)
588             self.tails[tail].remove(share.hash)
589             if not self.tails[share.previous_hash]:
590                 self.tails.pop(share.previous_hash)
591         elif share.hash in self.heads:
592             tail = self.heads.pop(share.hash)
593             self.tails[tail].remove(share.hash)
594             if self.reverse_shares[share.previous_hash] != set([share.hash]):
595                 pass # has sibling
596             else:
597                 self.tails[tail].add(share.previous_hash)
598                 self.heads[share.previous_hash] = tail
599         elif share.previous_hash in self.tails:
600             heads = self.tails[share.previous_hash]
601             if len(self.reverse_shares[share.previous_hash]) > 1:
602                 raise NotImplementedError()
603             else:
604                 del self.tails[share.previous_hash]
605                 for head in heads:
606                     self.heads[head] = share.hash
607                 self.tails[share.hash] = set(heads)
608         else:
609             raise NotImplementedError()
610         
611         # move ref pointing to this up
612         if share.previous_hash in self.reverse_height_refs:
613             assert share.hash not in self.reverse_height_refs, list(self.reverse_heights.get(self.reverse_height_refs.get(share.hash, object()), set()))
614             
615             ref = self.reverse_height_refs[share.previous_hash]
616             cur_height, cur_hash, cur_work = self.height_refs[ref]
617             assert cur_hash == share.previous_hash
618             self.height_refs[ref] = cur_height - 1, share.hash, cur_work - target_to_average_attempts(share.target)
619             del self.reverse_height_refs[share.previous_hash]
620             self.reverse_height_refs[share.hash] = ref
621         
622         # delete height entry, and ref if it is empty
623         if share.hash in self.heights:
624             _, ref, _ = self.heights.pop(share.hash)
625             self.reverse_heights[ref].remove(share.hash)
626             if not self.reverse_heights[ref]:
627                 del self.reverse_heights[ref]
628                 _, ref_hash, _ = self.height_refs.pop(ref)
629                 del self.reverse_height_refs[ref_hash]
630         
631         self.shares.pop(share.hash)
632         self.reverse_shares[share.previous_hash].remove(share.hash)
633         if not self.reverse_shares[share.previous_hash]:
634             self.reverse_shares.pop(share.previous_hash)
635         
636         #assert self.test() is None
637         self.removed.happened(share)
638     
639     def get_height(self, share_hash):
640         height, work, last = self.get_height_work_and_last(share_hash)
641         return height
642     
643     def get_work(self, share_hash):
644         height, work, last = self.get_height_work_and_last(share_hash)
645         return work
646     
647     def get_last(self, share_hash):
648         height, work, last = self.get_height_work_and_last(share_hash)
649         return last
650     
651     def get_height_and_last(self, share_hash):
652         height, work, last = self.get_height_work_and_last(share_hash)
653         return height, last
654     
655     def _get_height_jump(self, share_hash):
656         if share_hash in self.heights:
657             height_to1, ref, work_inc1 = self.heights[share_hash]
658             height_to2, share_hash, work_inc2 = self.height_refs[ref]
659             height_inc = height_to1 + height_to2
660             work_inc = work_inc1 + work_inc2
661         else:
662             height_inc, share_hash, work_inc = 1, self.shares[share_hash].previous_hash, target_to_average_attempts(self.shares[share_hash].target)
663         return height_inc, share_hash, work_inc
664     
665     def _set_height_jump(self, share_hash, height_inc, other_share_hash, work_inc):
666         if other_share_hash not in self.reverse_height_refs:
667             ref = self.ref_generator.next()
668             assert ref not in self.height_refs
669             self.height_refs[ref] = 0, other_share_hash, 0
670             self.reverse_height_refs[other_share_hash] = ref
671             del ref
672         
673         ref = self.reverse_height_refs[other_share_hash]
674         ref_height_to, ref_share_hash, ref_work_inc = self.height_refs[ref]
675         assert ref_share_hash == other_share_hash
676         
677         if share_hash in self.heights:
678             prev_ref = self.heights[share_hash][1]
679             self.reverse_heights[prev_ref].remove(share_hash)
680             if not self.reverse_heights[prev_ref] and prev_ref != ref:
681                 self.reverse_heights.pop(prev_ref)
682                 _, x, _ = self.height_refs.pop(prev_ref)
683                 self.reverse_height_refs.pop(x)
684         self.heights[share_hash] = height_inc - ref_height_to, ref, work_inc - ref_work_inc
685         self.reverse_heights.setdefault(ref, set()).add(share_hash)
686     
687     def get_height_work_and_last(self, share_hash):
688         assert isinstance(share_hash, (int, long, type(None)))
689         orig = share_hash
690         height = 0
691         work = 0
692         updates = []
693         while share_hash in self.shares:
694             updates.append((share_hash, height, work))
695             height_inc, share_hash, work_inc = self._get_height_jump(share_hash)
696             height += height_inc
697             work += work_inc
698         for update_hash, height_then, work_then in updates:
699             self._set_height_jump(update_hash, height - height_then, share_hash, work - work_then)
700         return height, work, share_hash
701     
702     def get_chain_known(self, start_hash):
703         assert isinstance(start_hash, (int, long, type(None)))
704         '''
705         Chain starting with item of hash I{start_hash} of items that this Tracker contains
706         '''
707         item_hash_to_get = start_hash
708         while True:
709             if item_hash_to_get not in self.shares:
710                 break
711             share = self.shares[item_hash_to_get]
712             assert not isinstance(share, long)
713             yield share
714             item_hash_to_get = share.previous_hash
715     
716     def get_chain_to_root(self, start_hash, root=None):
717         assert isinstance(start_hash, (int, long, type(None)))
718         assert isinstance(root, (int, long, type(None)))
719         '''
720         Chain of hashes starting with share_hash of shares to the root (doesn't include root)
721         Raises an error if one is missing
722         '''
723         share_hash_to_get = start_hash
724         while share_hash_to_get != root:
725             share = self.shares[share_hash_to_get]
726             yield share
727             share_hash_to_get = share.previous_hash
728     
729     def get_best_hash(self):
730         '''
731         Returns hash of item with the most items in its chain
732         '''
733         if not self.heads:
734             return None
735         return max(self.heads, key=self.get_height_and_last)
736     
737     def get_highest_height(self):
738         return max(self.get_height_and_last(head)[0] for head in self.heads) if self.heads else 0
739
740 class FakeShare(object):
741     def __init__(self, **kwargs):
742         self.__dict__.update(kwargs)
743
744 if __name__ == '__main__':
745     
746     t = Tracker()
747     
748     for i in xrange(10000):
749         t.add(FakeShare(hash=i, previous_hash=i - 1 if i > 0 else None))
750     
751     #t.remove(99)
752     
753     print 'HEADS', t.heads
754     print 'TAILS', t.tails
755     
756     import random
757     
758     while False:
759         print
760         print '-'*30
761         print
762         t = Tracker()
763         for i in xrange(random.randrange(100)):
764             x = random.choice(list(t.shares) + [None])
765             print i, '->', x
766             t.add(FakeShare(i, x))
767         while t.shares:
768             x = random.choice(list(t.shares))
769             print 'DEL', x, t.__dict__
770             try:
771                 t.remove(x)
772             except NotImplementedError:
773                 print 'aborted; not implemented'
774         import time
775         time.sleep(.1)
776         print 'HEADS', t.heads
777         print 'TAILS', t.tails
778     
779     #for share_hash, share in sorted(t.shares.iteritems()):
780     #    print share_hash, share.previous_hash, t.heads.get(share_hash), t.tails.get(share_hash)
781     
782     #import sys;sys.exit()
783     
784     print t.get_nth_parent_hash(9000, 5000)
785     print t.get_nth_parent_hash(9001, 412)
786     #print t.get_nth_parent_hash(90, 51)
787     
788     for share_hash in sorted(t.shares):
789         print str(share_hash).rjust(4),
790         x = t.skips.get(share_hash, None)
791         if x is not None:
792             print str(x[0]).rjust(4),
793             for a in x[1]:
794                 print str(a).rjust(10),
795         print
796
797 # network definitions
798
799 class Mainnet(object):
800     BITCOIN_P2P_PREFIX = 'f9beb4d9'.decode('hex')
801     BITCOIN_P2P_PORT = 8333
802     BITCOIN_ADDRESS_VERSION = 0
803     BITCOIN_RPC_CHECK = staticmethod(defer.inlineCallbacks(lambda bitcoind: defer.returnValue('name_firstupdate' not in (yield bitcoind.rpc_help()) and not (yield bitcoind.rpc_getinfo())['testnet'])))
804
805 class Testnet(object):
806     BITCOIN_P2P_PREFIX = 'fabfb5da'.decode('hex')
807     BITCOIN_P2P_PORT = 18333
808     BITCOIN_ADDRESS_VERSION = 111
809     BITCOIN_RPC_CHECK = staticmethod(defer.inlineCallbacks(lambda bitcoind: defer.returnValue('name_firstupdate' not in (yield bitcoind.rpc_help()) and (yield bitcoind.rpc_getinfo())['testnet'])))