from __future__ import division import struct import hashlib import warnings from . import base58 from p2pool.util import bases, math class EarlyEnd(Exception): pass class LateEnd(Exception): pass def read((data, pos), length): data2 = data[pos:pos + length] if len(data2) != length: raise EarlyEnd() return data2, (data, pos + length) class Type(object): # the same data can have only one unpacked representation, but multiple packed binary representations #def __hash__(self): # return hash(tuple(self.__dict__.items())) #def __eq__(self, other): # if not isinstance(other, Type): # raise NotImplementedError() # return self.__dict__ == other.__dict__ def _unpack(self, data): obj, (data2, pos) = self.read((data, 0)) assert data2 is data if pos != len(data): raise LateEnd() return obj def _pack(self, obj): f = self.write(None, obj) res = [] while f is not None: res.append(f[1]) f = f[0] res.reverse() return ''.join(res) def unpack(self, data): obj = self._unpack(data) if __debug__: data2 = self._pack(obj) if data2 != data: assert self._unpack(data2) == obj return obj def pack(self, obj): data = self._pack(obj) assert self._unpack(data) == obj return data def pack_base58(self, obj): return base58.base58_encode(self.pack(obj)) def unpack_base58(self, base58_data): return self.unpack(base58.base58_decode(base58_data)) def hash160(self, obj): return ShortHashType().unpack(hashlib.new('ripemd160', hashlib.sha256(self.pack(obj)).digest()).digest()) def hash256(self, obj): return HashType().unpack(hashlib.sha256(hashlib.sha256(self.pack(obj)).digest()).digest()) class VarIntType(Type): # redundancy doesn't matter here because bitcoin and p2pool both reencode before hashing def read(self, file): data, file = read(file, 1) first = ord(data) if first < 0xfd: return first, file elif first == 0xfd: desc, length = '> 24) - 3)) assert target == self._bits_to_target1(struct.pack('= 128: n = '\x00' + n bits2 = (chr(len(n)) + (n + 3*chr(0))[:3])[::-1] bits = struct.unpack('H')), ]) tx_type = ComposedType([ ('version', StructType(' 1: hash_list = [merkle_record_type.hash256(dict(left=left, right=left if right is None else right)) for left, right in zip(hash_list[::2], hash_list[1::2] + [None])] return hash_list[0] def target_to_average_attempts(target): return 2**256//(target + 1) # human addresses human_address_type = ChecksummedType(ComposedType([ ('version', StructType(' share 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, other_share_hash self.skips = {} # share_hash -> skip list def add(self, share): assert not isinstance(share, (int, long, type(None))) if share.hash in self.shares: return # XXX raise exception? self.shares[share.hash] = share self.reverse_shares.setdefault(share.previous_hash, set()).add(share.hash) 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 = share.previous_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 def get_height_and_last(self, share_hash): assert isinstance(share_hash, (int, long, type(None))) orig = share_hash height = 0 updates = [] while True: if share_hash is None or share_hash not in self.shares: break updates.append((share_hash, height)) if share_hash in self.heights: height_inc, share_hash = self.heights[share_hash] else: height_inc, share_hash = 1, self.shares[share_hash].previous_hash height += height_inc for update_hash, height_then in updates: self.heights[update_hash] = height - height_then, share_hash assert (height, share_hash) == self.get_height_and_last2(orig), ((height, share_hash), self.get_height_and_last2(orig)) return height, share_hash def get_height_and_last2(self, share_hash): assert isinstance(share_hash, (int, long, type(None))) height = 0 while True: if share_hash not in self.shares: break share_hash = self.shares[share_hash].previous_hash height += 1 return height, 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 get_nth_parent_hash(self, item_hash, n): if n < 0: raise ValueError('n must be >= 0') updates = {} while n: if item_hash not in self.skips: self.skips[item_hash] = math.geometric(.5), [(1, self.shares[item_hash].previous_hash)] skip_length, skip = self.skips[item_hash] for i in xrange(skip_length): if i in updates: n_then, that_hash = updates.pop(i) x, y = self.skips[that_hash] assert len(y) == i y.append((n_then - n, item_hash)) for i in xrange(len(skip), skip_length): updates[i] = n, item_hash for i, (dist, then_hash) in enumerate(reversed(skip)): if dist <= n: break else: raise AssertionError() n -= dist item_hash = then_hash return item_hash def get_nth_parent2(self, item_hash, n): x = item_hash for i in xrange(n): x = self.shares[item_hash].previous_hash return x if __name__ == '__main__': class FakeShare(object): def __init__(self, hash, previous_hash): self.hash = hash self.previous_hash = previous_hash t = Tracker() for i in xrange(10000): t.add(FakeShare(i, i - 1 if i > 0 else None)) #for share_hash in sorted(t.shares): # print share_hash, t.get_height_and_last(share_hash) 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 # network definitions class Mainnet(object): BITCOIN_P2P_PREFIX = 'f9beb4d9'.decode('hex') BITCOIN_P2P_PORT = 8333 BITCOIN_ADDRESS_VERSION = 0 class Testnet(object): BITCOIN_P2P_PREFIX = 'fabfb5da'.decode('hex') BITCOIN_P2P_PORT = 18333 BITCOIN_ADDRESS_VERSION = 111