speed optimizations for parsing
authorforrest <forrest@470744a7-cac9-478e-843e-5ec1b25c69e8>
Tue, 12 Jul 2011 07:03:48 +0000 (07:03 +0000)
committerforrest <forrest@470744a7-cac9-478e-843e-5ec1b25c69e8>
Tue, 12 Jul 2011 07:03:48 +0000 (07:03 +0000)
git-svn-id: svn://forre.st/p2pool@1374 470744a7-cac9-478e-843e-5ec1b25c69e8

p2pool/bitcoin/data.py
p2pool/bitcoin/p2p.py
p2pool/data.py
p2pool/util/math.py

index 551fef9..a16a920 100644 (file)
@@ -1,7 +1,7 @@
 from __future__ import division
 
 import struct
-import StringIO
+import cStringIO as StringIO
 import hashlib
 import warnings
 
@@ -38,9 +38,10 @@ class Type(object):
     def unpack(self, data):
         obj = self._unpack(data)
         
-        data2 = self._pack(obj)
-        if data2 != data:
-            assert self._unpack(data2) == obj
+        if __debug__:
+            data2 = self._pack(obj)
+            if data2 != data:
+                assert self._unpack(data2) == obj
         
         return obj
     
@@ -79,11 +80,17 @@ class VarIntType(Type):
         data = file.read(1)
         if len(data) != 1:
             raise EarlyEnd()
-        first, = struct.unpack('<B', data)
-        if first == 0xff: desc = '<Q'
-        elif first == 0xfe: desc = '<I'
-        elif first == 0xfd: desc = '<H'
-        else: return first
+        first = ord(data)
+        if first < 0xfd:
+            return first
+        elif first == 0xfd:
+            desc = '<H'
+        elif first == 0xfe:
+            desc = '<I'
+        elif first == 0xff:
+            desc = '<Q'
+        else:
+            raise AssertionError()
         length = struct.calcsize(desc)
         data = file.read(length)
         if len(data) != length:
@@ -103,15 +110,17 @@ class VarIntType(Type):
             raise ValueError('int too large for varint')
 
 class VarStrType(Type):
+    _inner_size = VarIntType()
+    
     def read(self, file):
-        length = VarIntType().read(file)
+        length = self._inner_size.read(file)
         res = file.read(length)
         if len(res) != length:
             raise EarlyEnd('var str not long enough %r' % ((length, len(res), res),))
         return res
     
     def write(self, file, item):
-        VarIntType().write(file, len(item))
+        self._inner_size.write(file, len(item))
         file.write(item)
 
 class FixedStrType(Type):
@@ -154,10 +163,10 @@ class HashType(Type):
         return int(data[::-1].encode('hex'), 16)
     
     def write(self, file, item):
-        if item >= 2**256:
+        if not 0 <= item < 2**256:
             raise ValueError("invalid hash value")
         if item != 0 and item < 2**160:
-            warnings.warn("very low hash value - maybe you meant to use ShortHashType?")
+            warnings.warn("very low hash value - maybe you meant to use ShortHashType? %x" % (item,))
         file.write(('%064x' % (item,)).decode('hex')[::-1])
 
 class ShortHashType(Type):
@@ -173,18 +182,28 @@ class ShortHashType(Type):
         file.write(('%040x' % (item,)).decode('hex')[::-1])
 
 class ListType(Type):
+    _inner_size = VarIntType()
+    
     def __init__(self, type):
         self.type = type
     
     def read(self, file):
-        length = VarIntType().read(file)
+        length = self._inner_size.read(file)
         return [self.type.read(file) for i in xrange(length)]
     
     def write(self, file, item):
-        VarIntType().write(file, len(item))
+        self._inner_size.write(file, len(item))
         for subitem in item:
             self.type.write(file, subitem)
 
+class FastLittleEndianUnsignedInteger(Type):
+    def read(self, file):
+        data = map(ord, file.read(4))
+        return data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24)
+    
+    def write(self, file, item):
+        StructType("<I").write(file, item)
+
 class StructType(Type):
     def __init__(self, desc):
         self.desc = desc
@@ -256,47 +275,42 @@ class ChecksummedType(Type):
 class FloatingIntegerType(Type):
     # redundancy doesn't matter here because bitcoin checks binary bits against its own computed bits
     # so it will always be encoded 'normally' in blocks (they way bitcoin does it)
+    _inner = StructType("<I")
+    _inner = FastLittleEndianUnsignedInteger()
     
     def read(self, file):
-        data = FixedStrType(4).read(file)
-        target = self._bits_to_target(data)
-        if self._target_to_bits(target) != data:
-            raise ValueError("bits in non-canonical form")
+        bits = self._inner.read(file)
+        target = self._bits_to_target(bits)
+        if __debug__:
+            if self._target_to_bits(target) != bits:
+                raise ValueError("bits in non-canonical form")
         return target
     
     def write(self, file, item):
-        FixedStrType(4).write(file, self._target_to_bits(item))
+        self._inner.write(file, self._target_to_bits(item))
     
     def truncate_to(self, x):
         return self._bits_to_target(self._target_to_bits(x, _check=False))
         
-    def _bits_to_target(self, bits, _check=True):
-        assert len(bits) == 4, repr(bits)
-        target1 = self._bits_to_target1(bits)
-        target2 = self._bits_to_target2(bits)
-        if target1 != target2:
-            raise ValueError()
-        if _check:
-            if self._target_to_bits(target1, _check=False) != bits:
-                raise ValueError()
-        return target1
+    def _bits_to_target(self, bits2):
+        target = math.shift_left(bits2 & 0x00ffffff, 8 * ((bits2 >> 24) - 3))
+        assert target == self._bits_to_target1(struct.pack("<I", bits2))
+        assert self._target_to_bits(target, _check=False) == bits2
+        return target
     
     def _bits_to_target1(self, bits):
         bits = bits[::-1]
         length = ord(bits[0])
         return bases.string_to_natural((bits[1:] + "\0"*length)[:length])
 
-    def _bits_to_target2(self, bits):
-        bits = struct.unpack("<I", bits)[0]
-        return math.shift_left(bits & 0x00ffffff, 8 * ((bits >> 24) - 3))
-
     def _target_to_bits(self, target, _check=True):
         n = bases.natural_to_string(target)
         if n and ord(n[0]) >= 128:
             n = "\x00" + n
-        bits = (chr(len(n)) + (n + 3*chr(0))[:3])[::-1]
+        bits2 = (chr(len(n)) + (n + 3*chr(0))[:3])[::-1]
+        bits = struct.unpack("<I", bits2)[0]
         if _check:
-            if self._bits_to_target(bits, _check=False) != target:
+            if self._bits_to_target(bits) != target:
                 raise ValueError(repr((target, self._bits_to_target(bits, _check=False))))
         return bits
 
index b756c2d..dd4ed40 100644 (file)
@@ -155,7 +155,9 @@ class Protocol(BaseProtocol):
     def handle_verack(self):
         self.version = self.version_after
         
-        # connection ready
+        self.ready()
+    
+    def ready(self):
         self.check_order = deferral.GenericDeferrer(2**256, lambda id, order: self.send_checkorder(id=id, order=order))
         self.submit_order = deferral.GenericDeferrer(2**256, lambda id, order: self.send_submitorder(id=id, order=order))
         self.get_block = deferral.ReplyMatcher(lambda hash: self.send_getdata(requests=[dict(type='block', hash=hash)]))
@@ -191,12 +193,12 @@ class Protocol(BaseProtocol):
     message_getblocks = bitcoin_data.ComposedType([
         ('version', bitcoin_data.StructType('<I')),
         ('have', bitcoin_data.ListType(bitcoin_data.HashType())),
-        ('last', bitcoin_data.HashType()),
+        ('last', bitcoin_data.PossiblyNone(0, bitcoin_data.HashType())),
     ])
     message_getheaders = bitcoin_data.ComposedType([
         ('version', bitcoin_data.StructType('<I')),
         ('have', bitcoin_data.ListType(bitcoin_data.HashType())),
-        ('last', bitcoin_data.HashType()),
+        ('last', bitcoin_data.PossiblyNone(0, bitcoin_data.HashType())),
     ])
     message_getaddr = bitcoin_data.ComposedType([])
     message_checkorder = bitcoin_data.ComposedType([
@@ -231,16 +233,18 @@ class Protocol(BaseProtocol):
         self.get_block.got_response(bitcoin_data.block_header_type.hash256(block['header']), block)
     
     message_headers = bitcoin_data.ComposedType([
-        ('headers', bitcoin_data.ListType(bitcoin_data.block_header_type)),
+        ('headers', bitcoin_data.ListType(bitcoin_data.block_type)),
     ])
     def handle_headers(self, headers):
         for header in headers:
-            self.get_block_header.got_response(bitcoin_data.block_hash(header), header)
+            header = header['header']
+            print header
+            self.get_block_header.got_response(bitcoin_data.block_header_type.hash256(header), header)
     
     message_reply = bitcoin_data.ComposedType([
         ('hash', bitcoin_data.HashType()),
         ('reply',  bitcoin_data.EnumType(bitcoin_data.StructType('<I'), {'success': 0, 'failure': 1, 'denied': 2})),
-        ('script', bitcoin_data.VarStrType()),
+        ('script', bitcoin_data.PossiblyNone("", bitcoin_data.VarStrType())),
     ])
     def handle_reply(self, hash, reply, script):
         self.check_order.got_response(hash, dict(reply=reply, script=script))
index 1e795b3..b3f2f86 100644 (file)
@@ -271,9 +271,7 @@ def generate_transaction(tracker, previous_share_hash, new_script, subsidy, nonc
 
 
 class Tracker(object):
-    def __init__(self, net):
-        self.net = net
-        
+    def __init__(self):        
         self.shares = {} # hash -> share
         self.reverse_shares = {} # previous_share_hash -> set of share_hashes
         
@@ -385,8 +383,9 @@ if __name__ == '__main__':
 
 class OkayTracker(Tracker):
     def __init__(self, net):
-        Tracker.__init__(self, net)
-        self.verified = set()
+        Tracker.__init__(self)
+        self.net = net
+        self.verified = Tracker()
     """
         self.okay_cache = {} # hash -> height
     
@@ -418,25 +417,18 @@ class OkayTracker(Tracker):
     """
     def think(self):
         desired = set()
-        best = None
-        best_score = 0
+        
+        # for each overall head, attempt verification
+        # if it fails, attempt on parent, and repeat
+        # if no successful verification because of lack of parents, request parent
         for head in self.heads:
-            height, last = self.get_height_and_last(head)
-            #if height > 1:#, self.shares[head]
-            if last is not None and height < 2*self.net.CHAIN_LENGTH:
-                desired.add((random.choice(self.reverse_shares[last]).peers, last)) # XXX
-                continue
-            #first_to_verify = math.nth(self.get_chain(head), self.net.CHAIN_LENGTH - 1)
-            to_verify = []
-            last_good_hash = None
-            for share_hash in self.get_chain_known(head):
-                if share_hash in self.verified:
-                    last_good = share_hash
+            head_height, last = self.get_height_and_last(head)
+            if head_height < a and last is not None:
+                # request more
+            
+            for share in itertools.islice(self.get_chain_known(head), None if last is None else head_height - self.net.CHAIN_LENGTH): # XXX change length for None
+                in share in self.verified.shares:
                     break
-                to_verify.append(share_hash)
-            # recheck for 2*chain_length
-            for share_hash in reversed(to_verify):
-                share = self.shares[share_hash]
                 try:
                     share.check(self, self.net)
                 except:
@@ -444,31 +436,24 @@ class OkayTracker(Tracker):
                     print "Share check failed:"
                     traceback.print_exc()
                     print
+                else:
+                    self.verified.add_share(share_hash)
                     break
-                self.verified.add(share_hash)
-                last_good_hash = share_hash
-            
-            new_height, last = self.get_height_and_last(last_good_hash)
-            
-            print head, height, last, new_height
-            score = new_height
-            
-            if score > best_score:
-                best = head
-                best_score = score
         
-        print "BEST", best
+        # try to get at least CHAIN_LENGTH height for each verified head, requesting parents if needed
+        for head in self.verified.heads:
+            head_height, last = self.verified.get_height_and_last(head)
+            a
+        
+        # decide best verified head
+        def score(share_hash):
+            share = self.verified.shares[share_hash]
+            head_height, last = self.verified.get_height_and_last(share)
+            return (min(head_height, net.CHAIN_LENGTH), RECENTNESS)
+        best = max(self.verified.heads, key=score)
+        
         return best, desired
 
-class Chain(object):
-    def __init__(self):
-        pass
-
-def get_chain_descriptor(tracker, start):
-    for item in tracker.get_chain(self.net.CHAIN_LENGTH):
-        a
-    pass
-
 
 class Mainnet(bitcoin_data.Mainnet):
     SHARE_PERIOD = 5 # seconds
index 67a883d..19f77bd 100644 (file)
@@ -18,9 +18,9 @@ def shuffled(x):
 
 def shift_left(n, m):
     # python: :(
-    if m < 0:
-        return n >> -m
-    return n << m
+    if m >= 0:
+        return n << m
+    return n >> -m
 
 def clip(x, (low, high)):
     if x < low: