style cleanup
[p2pool.git] / p2pool / data.py
index b3f2f86..bf1295d 100644 (file)
@@ -1,7 +1,9 @@
 from __future__ import division
 
 import itertools
-import traceback
+import random
+
+from twisted.python import log
 
 from p2pool.util import math
 from p2pool.bitcoin import data as bitcoin_data
@@ -11,17 +13,17 @@ class CompressedList(bitcoin_data.Type):
         self.inner = inner
     
     def read(self, file):
-        values = bitcoin_data.ListType(self.inner).read(file)
+        values, file = bitcoin_data.ListType(self.inner).read(file)
         if values != sorted(set(values)):
-            raise ValueError("invalid values")
-        references = bitcoin_data.ListType(bitcoin_data.VarIntType()).read(file)
-        return [values[reference] for reference in references]
+            raise ValueError('invalid values')
+        references, file = bitcoin_data.ListType(bitcoin_data.VarIntType()).read(file)
+        return [values[reference] for reference in references], file
     
     def write(self, file, item):
         values = sorted(set(item))
         values_map = dict((value, i) for i, value in enumerate(values))
-        bitcoin_data.ListType(self.inner).write(file, values)
-        bitcoin_data.ListType(bitcoin_data.VarIntType()).write(file, [values_map[subitem] for subitem in item])
+        file = bitcoin_data.ListType(self.inner).write(file, values)
+        return bitcoin_data.ListType(bitcoin_data.VarIntType()).write(file, [values_map[subitem] for subitem in item])
 
 
 merkle_branch_type = bitcoin_data.ListType(bitcoin_data.ComposedType([
@@ -112,6 +114,7 @@ def share_info_to_gentx(share_info, block_target, tracker, net):
 
 class Share(object):
     peer = None
+    gentx = None
     
     @classmethod
     def from_block(cls, block):
@@ -128,6 +131,15 @@ class Share(object):
     def __init__(self, header, share_info, merkle_branch=None, other_txs=None):
         if merkle_branch is None and other_txs is None:
             raise ValueError('need either merkle_branch or other_txs')
+        if other_txs is not None:
+            new_merkle_branch = calculate_merkle_branch([dict(version=0, tx_ins=[], tx_outs=[], lock_time=0)] + other_txs, 0)
+            if merkle_branch is not None:
+                if merke_branch != new_merkle_branch:
+                    raise ValueError('invalid merkle_branch and other_txs')
+            merkle_branch = new_merkle_branch
+        
+        if len(merkle_branch) > 16:
+            raise ValueError('merkle_branch too long!')
         
         self.header = header
         self.share_info = share_info
@@ -140,25 +152,32 @@ class Share(object):
         self.new_script = self.share_info['new_script']
         self.subsidy = self.share_info['subsidy']
         
-        self.previous_share_hash = self.share_data['previous_share_hash']
+        if len(self.new_script) > 100:
+            raise ValueError('new_script too long!')
+        
+        self.previous_hash = self.previous_share_hash = self.share_data['previous_share_hash']
         self.previous_shares_hash = self.share_data['previous_shares_hash']
         self.target2 = self.share_data['target2']
+        self.nonce = self.share_data['nonce']
+        
+        if len(self.nonce) > 20:
+            raise ValueError('nonce too long!')
         
         self.hash = bitcoin_data.block_header_type.hash256(header)
         
         if self.hash > self.target2:
-            print "hash", hex(self.hash)
-            print "targ", hex(self.target2)
+            print 'hash', hex(self.hash)
+            print 'targ', hex(self.target2)
             raise ValueError('not enough work!')
         
         
         self.shared = False
     
     def as_block(self):
-        if self.txs is None:
+        if self.other_txs is None:
             raise ValueError('share does not contain all txs')
         
-        return dict(header=self.header, txs=self.txs)
+        return dict(header=self.header, txs=[self.gentx] + self.other_txs)
     
     def as_share1a(self):
         return dict(header=self.header, share_info=self.share_info, merkle_branch=self.merkle_branch)
@@ -167,15 +186,25 @@ class Share(object):
         return dict(header=self.header, share_info=self.share_info, other_txs=self.other_txs)
     
     def check(self, tracker, net):
+        import time
+        if self.previous_share_hash is not None:
+            if self.header['timestamp'] <= math.median((s.timestamp for s in itertools.islice(tracker.get_chain_to_root(self.previous_share_hash), 11)), use_float=False):
+                raise ValueError('share from too far in the past!')
+        
+        # XXX use adjusted time
+        if self.header['timestamp'] > time.time() + 2*60*60:
+            raise ValueError('share from too far in the future!')
+        
         gentx = share_info_to_gentx(self.share_info, self.header['target'], tracker, net)
         
-        if self.merkle_branch is not None:
-            if check_merkle_branch(gentx, self.merkle_branch) != self.header['merkle_root']:
-                raise ValueError("gentx doesn't match header via merkle_branch")
+        if check_merkle_branch(gentx, self.merkle_branch) != self.header['merkle_root']:
+            raise ValueError('''gentx doesn't match header via merkle_branch''')
         
         if self.other_txs is not None:
             if bitcoin_data.merkle_hash([gentx] + self.other_txs) != self.header['merkle_root']:
-                raise ValueError("gentx doesn't match header via other_txs")
+                raise ValueError('''gentx doesn't match header via other_txs''')
+        
+        self.gentx = gentx
         
         return Share2(self)
     
@@ -200,26 +229,26 @@ def generate_transaction(tracker, previous_share_hash, new_script, subsidy, nonc
     previous_share2 = tracker.shares[previous_share_hash] if previous_share_hash is not None else None
     #previous_share2 = chain.shares
     #previous_shares
-    #shares = 
+    #shares =
     #shares = (previous_share2.shares if previous_share2 is not None else [net.SCRIPT]*net.SPREAD)[1:-1] + [new_script, new_script]
     
-    lookbehind = 120
+    lookbehind = 200
     chain = list(itertools.islice(tracker.get_chain_to_root(previous_share_hash), lookbehind))
     if len(chain) < lookbehind:
-        target2 = bitcoin_data.FloatingIntegerType().truncate_to(2**256//2**16 - 1)
+        target2 = bitcoin_data.FloatingIntegerType().truncate_to(2**256//2**24 - 1)
     else:
-        attempts = sum(bitcoin_data.target_to_average_attempts(share.target2) for share in chain)
+        attempts = sum(bitcoin_data.target_to_average_attempts(share.target2) for share in chain[:-1])
         time = chain[0].timestamp - chain[-1].timestamp
         if time == 0:
             time = 1
         attempts_per_second = attempts//time
         pre_target = 2**256//(net.SHARE_PERIOD*attempts_per_second) - 1
         pre_target2 = math.clip(pre_target, (previous_share2.target2*9//10, previous_share2.target2*11//10))
-        pre_target3 = math.clip(pre_target2, (0, 2**256//2**16 - 1))
+        pre_target3 = math.clip(pre_target2, (0, 2**256//2**24 - 1))
         target2 = bitcoin_data.FloatingIntegerType().truncate_to(pre_target3)
-        print attempts_per_second//1000, "KHASH"
-        print "TARGET", 2**256//target2, 2**256/pre_target
-        print "ATT", bitcoin_data.target_to_average_attempts(target2)//1000
+        print attempts_per_second//1000, 'KHASH'
+        #print 'TARGET', 2**256//target2, 2**256/pre_target
+        #print 'ATT', bitcoin_data.target_to_average_attempts(target2)//1000
     
     
     attempts_to_block = bitcoin_data.target_to_average_attempts(block_target)
@@ -227,15 +256,17 @@ def generate_transaction(tracker, previous_share_hash, new_script, subsidy, nonc
     total_weight = 0
     
     class fake_share(object):
-        script = new_script
-        share = dict(target=target2)
+        pass
+    fake_share.new_script = new_script
+    fake_share.target2 = target2
     
     dest_weights = {}
     for i, share in enumerate(itertools.chain([fake_share], itertools.islice(tracker.get_chain_to_root(previous_share_hash), net.CHAIN_LENGTH))):
-        weight = bitcoin_data.target_to_average_attempts(share.share['target'])
-        weight = max(weight, max_weight - total_weight)
+        weight = bitcoin_data.target_to_average_attempts(share.target2)
+        if weight > max_weight - total_weight:
+            weight = max_weight - total_weight
         
-        dest_weights[share.script] = dest_weights.get(share.script, 0) + weight
+        dest_weights[share.new_script] = dest_weights.get(share.new_script, 0) + weight
         total_weight += weight
         
         if total_weight == max_weight:
@@ -270,187 +301,86 @@ def generate_transaction(tracker, previous_share_hash, new_script, subsidy, nonc
     )
 
 
-class Tracker(object):
-    def __init__(self):        
-        self.shares = {} # hash -> share
-        self.reverse_shares = {} # previous_share_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
-    
-    def add_share(self, share):
-        if share.hash in self.shares:
-            return # XXX raise exception?
-        
-        self.shares[share.hash] = share
-        self.reverse_shares.setdefault(share.previous_share_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_share_hash in self.heads:
-            tail = self.heads.pop(share.previous_share_hash)
-        else:
-            tail = share.previous_share_hash
-        
-        self.tails.setdefault(tail, set()).update(heads)
-        if share.previous_share_hash in self.tails[tail]:
-            self.tails[tail].remove(share.previous_share_hash)
-        
-        for head in heads:
-            self.heads[head] = tail
-    
-    def get_height_and_last(self, share_hash):
-        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_share_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):
-        height = 0
-        while True:
-            if share_hash not in self.shares:
-                break
-            share_hash = self.shares[share_hash].previous_share_hash
-            height += 1
-        return height, share_hash
-    
-    def get_chain_known(self, share_hash):
-        while True:
-            if share_hash not in self.shares:
-                break
-            yield share_hash
-            share_hash = self.shares[share_hash].previous_share_hash
-    
-    def get_chain_to_root(self, start):
-        share_hash_to_get = start
-        while share_hash_to_get is not None:
-            share = self.shares[share_hash_to_get]
-            yield share
-            share_hash_to_get = share.previous_share_hash
-    
-    
-    def get_best_share_hash(self):
-        return None
-        return max(self.heads, key=self.score_chain)
-    '''
-    def score_chain(self, start):
-        length = len(self.get_chain(start))
-        
-        score = 0
-        for share in itertools.islice(self.get_chain(start), self.net.CHAIN_LENGTH):
-            score += a
-        
-        return (min(length, 1000), score)
-    '''
 
-if __name__ == '__main__':
-    class FakeShare(object):
-        def __init__(self, hash, previous_share_hash):
-            self.hash = hash
-            self.previous_share_hash = previous_share_hash
-    
-    t = Tracker()
-    
-    t.add_share(FakeShare(1, 2))
-    print t.heads, t.tails
-    t.add_share(FakeShare(4, 0))
-    print t.heads, t.tails
-    t.add_share(FakeShare(3, 4))
-    print t.heads, t.tails
-    t.add_share(FakeShare(5, 0))
-    print t.heads, t.tails
-    t.add_share(FakeShare(0, 1))
-    print t.heads, t.tails
-    
-    for share_hash in t.shares:
-        print share_hash, t.get_height_and_last(share_hash)
-
-class OkayTracker(Tracker):
+class OkayTracker(bitcoin_data.Tracker):
     def __init__(self, net):
-        Tracker.__init__(self)
+        bitcoin_data.Tracker.__init__(self)
         self.net = net
-        self.verified = Tracker()
-    """
+        self.verified = bitcoin_data.Tracker()
+        
         self.okay_cache = {} # hash -> height
     
-    def is_okay(self, start, _height_after=0):
-        '''
-        Returns:
-            {'result': 'okay', verified_height: ...} # if share has an okay parent or if share has CHAIN_LENGTH children and CHAIN_LENTH parents that it verified with
-            {'result': 'needs_share', 'share_hash': ...} # if share doesn't have CHAIN_LENGTH parents
-            #{'result': 'needs_share_shares', 'share_hash': ...} # if share has CHAIN_LENGTH children and needs its shares to 
-            {'result': 'not_okay'} # if the share has a not okay parent or if the share has an okay parent and failed validation
-        '''
-        
-        if start in self.okay_cache:
-            return dict(result='okay', verified_height=self.okay_cache['start'])
-        
-        share = self.shares[start]
-        if start not in self.shares:
-            return dict(result='needs_share', share_hash=start)
-        
-        length = len
-        to_end_rev = []
-        for share in itertools.islice(self.get_chain(start), self.net.CHAIN_LENGTH):
-            if share in self.okay_cache:
-                return validate(share, to_end_rev[::-1])
-            to_end_rev.append(share)
-        # picking up last share from for loop, ew
-        self.okay_cache.add(share)
-        return validate(share, to_end_rev[::-1])
-    """
-    def think(self):
+    def attempt_verify(self, share):
+        height, last = self.get_height_and_last(share.hash)
+        if height < self.net.CHAIN_LENGTH and last is not None:
+            raise AssertionError()
+        if share.hash in self.verified.shares:
+            return True
+        try:
+            share.check(self, self.net)
+        except:
+            print
+            print 'Share check failed:'
+            log.err()
+            print
+            return False
+        else:
+            self.verified.add(share)
+            return True
+    
+    def think(self, ht):
         desired = set()
         
+        # O(len(self.heads))
+        #   make 'unverified heads' set?
         # 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:
             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
-                try:
-                    share.check(self, self.net)
-                except:
-                    print
-                    print "Share check failed:"
-                    traceback.print_exc()
-                    print
-                else:
-                    self.verified.add_share(share_hash)
+            for share in itertools.islice(self.get_chain_known(head), None if last is None else head_height - self.net.CHAIN_LENGTH):
+                if self.attempt_verify(share):
                     break
+            else:
+                desired.add((self.shares[random.choice(list(self.reverse_shares[last]))].peer, last))
         
         # 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
+            head_height, last_hash = self.verified.get_height_and_last(head)
+            last_height, last_last_hash = self.get_height_and_last(last_hash)
+            # XXX review boundary conditions
+            want = self.net.CHAIN_LENGTH - head_height
+            can = max(last_height - 1 - self.net.CHAIN_LENGTH, 0) if last_last_hash is not None else last_height
+            if want > can:
+                get = can
+            else:
+                get = want
+            #print 'Z', head_height, last_hash is None, last_height, last_last_hash is None, want, can, get
+            for share in itertools.islice(self.get_chain_known(last_hash), get):
+                if not self.attempt_verify(share):
+                    break
+            if head_height < self.net.CHAIN_LENGTH and last_last_hash is not None:
+                desired.add((self.verified.shares[random.choice(list(self.verified.reverse_shares[last_hash]))].peer, last_last_hash))
         
         # 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)
+            head_height, last = self.verified.get_height_and_last(share_hash)
+            score2 = 0
+            attempts = 0
+            max_height = 0
+            # XXX should only look past a certain share, not at recent ones
+            share2_hash = self.verified.get_nth_parent(share_hash, self.net.CHAIN_LENGTH//2)
+            for share in itertools.islice(self.verified.get_chain_known(share2_hash), self.net.CHAIN_LENGTH):
+                max_height = max(max_height, ht.get_min_height(share.header['previous_block']))
+                attempts += bitcoin_data.target_to_average_attempts(share.target2)
+                this_score = attempts//(ht.get_highest_height() - max_height + 1)
+                if this_score > score2:
+                    score2 = this_score
+            res = (min(head_height, self.net.CHAIN_LENGTH), score2)
+            print res
+            return res
+        best = max(self.verified.heads, key=score) if self.verified.heads else None
         
         return best, desired