Merge branch 'master' into optional_donation
[p2pool.git] / p2pool / data.py
index 0a51836..d90d45f 100644 (file)
@@ -50,6 +50,32 @@ share1b_type = bitcoin_data.ComposedType([
     ('other_txs', bitcoin_data.ListType(bitcoin_data.tx_type)),
 ])
 
+new_share_data_type = bitcoin_data.ComposedType([
+    ('previous_share_hash', bitcoin_data.PossiblyNoneType(0, bitcoin_data.HashType())),
+    ('coinbase', bitcoin_data.VarStrType()),
+    ('nonce', bitcoin_data.VarStrType()),
+    ('new_script', bitcoin_data.VarStrType()),
+    ('subsidy', bitcoin_data.StructType('<Q')),
+    ('donation', bitcoin_data.StructType('<H')),
+])
+
+new_share_info_type = bitcoin_data.ComposedType([
+    ('new_share_data', new_share_data_type),
+    ('target', bitcoin_data.FloatingIntegerType()),
+])
+
+new_share1a_type = bitcoin_data.ComposedType([
+    ('header', bitcoin_data.block_header_type),
+    ('share_info', new_share_info_type),
+    ('merkle_branch', bitcoin_data.merkle_branch_type),
+])
+
+new_share1b_type = bitcoin_data.ComposedType([
+    ('header', bitcoin_data.block_header_type),
+    ('share_info', new_share_info_type),
+    ('other_txs', bitcoin_data.ListType(bitcoin_data.tx_type)),
+])
+
 def calculate_merkle_branch(txs, index):
     hash_list = [(bitcoin_data.tx_type.hash256(tx), i == index, []) for i, tx in enumerate(txs)]
     
@@ -78,13 +104,6 @@ def check_merkle_branch(tx, branch):
             hash_ = bitcoin_data.merkle_record_type.hash256(dict(left=hash_, right=step['hash']))
     return hash_
 
-def gentx_to_share_info(gentx):
-    return dict(
-        share_data=coinbase_type.unpack(gentx['tx_ins'][0]['script'])['share_data'],
-        subsidy=sum(tx_out['value'] for tx_out in gentx['tx_outs']),
-        new_script=gentx['tx_outs'][-1]['script'],
-    )
-
 def share_info_to_gentx(share_info, block_target, tracker, net):
     return generate_transaction(
         tracker=tracker,
@@ -94,9 +113,11 @@ def share_info_to_gentx(share_info, block_target, tracker, net):
         nonce=share_info['share_data']['nonce'],
         block_target=block_target,
         net=net,
-    )
+    )[1]
 
 class Share(object):
+    donation = 0
+    
     @classmethod
     def from_block(cls, block, net):
         return cls(net, block['header'], gentx_to_share_info(block['txs'][0]), other_txs=block['txs'][1:])
@@ -180,6 +201,9 @@ class Share(object):
         return dict(header=self.header, share_info=self.share_info, merkle_branch=self.merkle_branch)
     
     def as_share1b(self):
+        if self.other_txs is None:
+            raise ValueError('share does not contain all txs')
+        
         return dict(header=self.header, share_info=self.share_info, other_txs=self.other_txs)
     
     def check(self, tracker, now, net):
@@ -212,6 +236,89 @@ class Share(object):
     def __repr__(self):
         return '<Share %s>' % (' '.join('%s=%r' % (k, getattr(self, k)) for k in self.__slots__),)
 
+class NewShare(Share):
+    def __init__(self, net, header, new_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 = bitcoin_data.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.previous_block = header['previous_block']
+        self.share_info = new_share_info
+        self.merkle_branch = merkle_branch
+        self.other_txs = other_txs
+        
+        self.timestamp = self.header['timestamp']
+        
+        self.share_data = self.share_info['new_share_data']
+        self.new_script = self.share_data['new_script']
+        self.subsidy = self.share_data['subsidy']
+        
+        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.target = self.share_info['target']
+        self.nonce = self.share_data['nonce']
+        
+        if len(self.nonce) > 100:
+            raise ValueError('nonce too long!')
+        
+        if getattr(net, 'BITCOIN_POW_SCRYPT', False):
+            self.bitcoin_hash = bitcoin_data.block_header_type.scrypt(header)
+        else:
+            self.bitcoin_hash = bitcoin_data.block_header_type.hash256(header)
+        
+        self.hash = new_share1a_type.hash256(self.as_share1a())
+        
+        if self.bitcoin_hash > self.target:
+            print 'hash %x' % self.bitcoin_hash
+            print 'targ %x' % self.target
+            raise ValueError('not enough work!')
+        
+        if script.get_sigop_count(self.new_script) > 1:
+            raise ValueError('too many sigops!')
+        
+        # XXX eww
+        self.time_seen = time.time()
+        self.shared = False
+        self.stored = False
+        self.peer = None
+    
+    def check(self, tracker, now, 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!')
+        
+        if self.header['timestamp'] > now + 2*60*60:
+            raise ValueError('share from too far in the future!')
+        
+        new_share_info, gentx = new_generate_transaction(tracker, self.share_info['new_share_data'], self.header['target'], net)
+        if new_share_info != self.share_info:
+            raise ValueError()
+        
+        if len(gentx['tx_ins'][0]['script']) > 100:
+            raise ValueError('''coinbase too large! %i bytes''' % (len(gentx['tx_ins'][0]['script']),))
+        
+        if bitcoin_data.check_merkle_branch(gentx, 0, 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''')
+            
+            if len(bitcoin_data.block_type.pack(dict(header=self.header, txs=[gentx] + self.other_txs))) > 1000000 - 1000:
+                raise ValueError('''block size too large''')
+
 def get_pool_attempts_per_second(tracker, previous_share_hash, net, dist=None):
     if dist is None:
         dist = net.TARGET_LOOKBEHIND
@@ -237,13 +344,13 @@ def generate_transaction(tracker, previous_share_hash, new_script, subsidy, nonc
         target = bitcoin_data.FloatingInteger.from_target_upper_bound(pre_target3)
     
     attempts_to_block = bitcoin_data.target_to_average_attempts(block_target)
-    max_weight = net.SPREAD * attempts_to_block
+    max_weight = 65535 * net.SPREAD * attempts_to_block
+    
+    this_weight = 65535 * min(bitcoin_data.target_to_average_attempts(target), max_weight)
+    other_weights, other_weights_total, other_donation_weight_total = tracker.get_cumulative_weights(previous_share_hash, min(height, net.CHAIN_LENGTH), max(0, max_weight - this_weight))
+    dest_weights, total_weight, donation_weight_total = math.add_dicts([{new_script: this_weight}, other_weights]), this_weight + other_weights_total, other_donation_weight_total
+    assert total_weight == sum(dest_weights.itervalues()) + donation_weight_total
     
-    this_weight = min(bitcoin_data.target_to_average_attempts(target), max_weight)
-    other_weights, other_weights_total = tracker.get_cumulative_weights(previous_share_hash, min(height, net.CHAIN_LENGTH), max(0, max_weight - this_weight))
-    dest_weights, total_weight = math.add_dicts([{new_script: this_weight}, other_weights]), this_weight + other_weights_total
-    assert total_weight == sum(dest_weights.itervalues())
-
     if net.SCRIPT:
         amounts = dict((script, subsidy*(396*weight)//(400*total_weight)) for (script, weight) in dest_weights.iteritems())
         amounts[new_script] = amounts.get(new_script, 0) + subsidy*2//400
@@ -253,7 +360,7 @@ def generate_transaction(tracker, previous_share_hash, new_script, subsidy, nonc
         amounts = dict((script, subsidy*(398*weight)//(400*total_weight)) for (script, weight) in dest_weights.iteritems())
         amounts[new_script] = amounts.get(new_script, 0) + subsidy*2//400
         amounts[new_script] = amounts.get(new_script, 0) + subsidy - sum(amounts.itervalues()) # collect any extra
-
+    
     if sum(amounts.itervalues()) != subsidy:
         raise ValueError()
     if any(x < 0 for x in amounts.itervalues()):
@@ -265,24 +372,96 @@ def generate_transaction(tracker, previous_share_hash, new_script, subsidy, nonc
     dests = sorted(pre_dests, key=lambda script: (script == new_script, script))
     assert dests[-1] == new_script
     
-    return dict(
+    share_data = dict(
+        previous_share_hash=previous_share_hash,
+        nonce=nonce,
+        target=target,
+    )
+    
+    share_info = dict(
+        share_data=share_data,
+        new_script=new_script,
+        subsidy=subsidy,
+    )
+    
+    return share_info, dict(
         version=1,
         tx_ins=[dict(
             previous_output=None,
             sequence=None,
             script=coinbase_type.pack(dict(
                 identifier=net.IDENTIFIER,
-                share_data=dict(
-                    previous_share_hash=previous_share_hash,
-                    nonce=nonce,
-                    target=target,
-                ),
+                share_data=share_data,
             )),
         )],
         tx_outs=[dict(value=amounts[script], script=script) for script in dests if amounts[script]],
         lock_time=0,
     )
 
+def new_generate_transaction(tracker, new_share_data, block_target, net):
+    previous_share_hash = new_share_data['previous_share_hash']
+    new_script = new_share_data['new_script']
+    subsidy = new_share_data['subsidy']
+    donation = new_share_data['donation']
+    assert 0 <= donation <= 65535
+    
+    height, last = tracker.get_height_and_last(previous_share_hash)
+    assert height >= net.CHAIN_LENGTH or last is None
+    if height < net.TARGET_LOOKBEHIND:
+        target = bitcoin_data.FloatingInteger.from_target_upper_bound(net.MAX_TARGET)
+    else:
+        attempts_per_second = get_pool_attempts_per_second(tracker, previous_share_hash, net)
+        previous_share = tracker.shares[previous_share_hash] if previous_share_hash is not None else None
+        pre_target = 2**256//(net.SHARE_PERIOD*attempts_per_second) - 1
+        pre_target2 = math.clip(pre_target, (previous_share.target*9//10, previous_share.target*11//10))
+        pre_target3 = math.clip(pre_target2, (0, net.MAX_TARGET))
+        target = bitcoin_data.FloatingInteger.from_target_upper_bound(pre_target3)
+    
+    attempts_to_block = bitcoin_data.target_to_average_attempts(block_target)
+    max_att = net.SPREAD * attempts_to_block
+    
+    this_att = min(bitcoin_data.target_to_average_attempts(target), max_att)
+    other_weights, other_total_weight, other_donation_weight = tracker.get_cumulative_weights(previous_share_hash, min(height, net.CHAIN_LENGTH), 65535*max(0, max_att - this_att))
+    assert other_total_weight == sum(other_weights.itervalues()) + other_donation_weight, (other_total_weight, sum(other_weights.itervalues()) + other_donation_weight)
+    weights, total_weight, donation_weight = math.add_dicts([{new_script: this_att*(65535-donation)}, other_weights]), this_att*65535 + other_total_weight, this_att*donation + other_donation_weight
+    assert total_weight == sum(weights.itervalues()) + donation_weight, (total_weight, sum(weights.itervalues()) + donation_weight)
+    
+    amounts = dict((script, subsidy*(199*weight)//(200*total_weight)) for (script, weight) in weights.iteritems())
+    amounts[new_script] = amounts.get(new_script, 0) + subsidy//200
+    amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy*(199*donation_weight)//(200*total_weight)
+    amounts[net.SCRIPT] = amounts.get(net.SCRIPT, 0) + subsidy - sum(amounts.itervalues()) # collect any extra satoshis :P
+    
+    if sum(amounts.itervalues()) != subsidy:
+        raise ValueError()
+    if any(x < 0 for x in amounts.itervalues()):
+        print amounts
+        import code
+        d = globals()
+        d.update(locals())
+        code.interact(local=d)
+        raise ValueError()
+    
+    pre_dests = sorted(amounts.iterkeys(), key=lambda script: (amounts[script], script))
+    pre_dests = pre_dests[-4000:] # block length limit, unlikely to ever be hit
+    
+    dests = sorted(pre_dests, key=lambda script: (script == new_script, script))
+    assert dests[-1] == new_script
+    
+    new_share_info = dict(
+        new_share_data=new_share_data,
+        target=target,
+    )
+    
+    return new_share_info, dict(
+        version=1,
+        tx_ins=[dict(
+            previous_output=None,
+            sequence=None,
+            script=new_share_data['coinbase'],
+        )],
+        tx_outs=[dict(value=0, script=bitcoin_data.HashType().pack(new_share_info_type.hash256(new_share_info)))] + [dict(value=amounts[script], script=script) for script in dests if amounts[script]],
+        lock_time=0,
+    )
 
 
 class OkayTracker(bitcoin_data.Tracker):
@@ -488,6 +667,14 @@ class ShareStore(object):
                             verified_hash = int(data_hex, 16)
                             yield 'verified_hash', verified_hash
                             verified_hashes.add(verified_hash)
+                        elif type_id == 3:
+                            share = NewShare.from_share1a(new_share1a_type.unpack(data_hex.decode('hex')), self.net)
+                            yield 'share', share
+                            share_hashes.add(share.hash)
+                        elif type_id == 4:
+                            share = NewShare.from_share1b(new_share1b_type.unpack(data_hex.decode('hex')), self.net)
+                            yield 'share', share
+                            share_hashes.add(share.hash)
                         else:
                             raise NotImplementedError("share type %i" % (type_id,))
                     except Exception:
@@ -507,7 +694,11 @@ class ShareStore(object):
         return filename
     
     def add_share(self, share):
-        if share.bitcoin_hash <= share.header['target']:
+        if isinstance(share, NewShare) and share.bitcoin_hash <= share.header['target']:
+            type_id, data = 4, new_share1b_type.pack(share.as_share1b())
+        elif isinstance(share, NewShare):
+            type_id, data = 3, new_share1a_type.pack(share.as_share1a())
+        elif share.bitcoin_hash <= share.header['target']:
             type_id, data = 1, share1b_type.pack(share.as_share1b())
         else:
             type_id, data = 0, share1a_type.pack(share.as_share1a())
@@ -558,7 +749,7 @@ class BitcoinMainnet(networks.BitcoinMainnet):
     NAME = 'bitcoin'
     P2P_PORT = 9333
     MAX_TARGET = 2**256//2**32 - 1
-    PERSIST = True
+    PERSIST = False
     WORKER_PORT = 9332
 
 class BitcoinTestnet(networks.BitcoinTestnet):