moved heighttracker to bitcoin.height_tracker
[p2pool.git] / p2pool / main.py
index 3e6dfc3..379522f 100644 (file)
@@ -5,7 +5,6 @@ import StringIO
 import argparse
 import os
 import random
-import struct
 import sys
 import time
 import signal
@@ -18,7 +17,7 @@ from twisted.python import log
 from nattraverso import portmapper, ipdiscover
 
 import bitcoin.p2p as bitcoin_p2p, bitcoin.getwork as bitcoin_getwork, bitcoin.data as bitcoin_data
-from bitcoin import worker_interface
+from bitcoin import worker_interface, height_tracker
 from util import expiring_dict, jsonrpc, variable, deferral, math, logging, pack
 from . import p2p, networks, web
 import p2pool, p2pool.data as p2pool_data
@@ -167,16 +166,7 @@ def main(args, net, datadir_path, merged_urls, worker_endpoint):
             ))
         yield set_real_work1()
         
-        if '\ngetblock ' in (yield deferral.retry()(bitcoind.rpc_help)()):
-            height_cacher = deferral.DeferredCacher(defer.inlineCallbacks(lambda block_hash: defer.returnValue((yield bitcoind.rpc_getblock('%x' % (block_hash,)))['blockcount'])))
-            best_height_cached = variable.Variable((yield deferral.retry()(height_cacher)(pre_current_work.value['previous_block'])))
-            def get_height_rel_highest(block_hash):
-                this_height = height_cacher.call_now(block_hash, 0)
-                best_height = height_cacher.call_now(pre_current_work.value['previous_block'], 0)
-                best_height_cached.set(max(best_height_cached.value, this_height, best_height))
-                return this_height - best_height_cached.value
-        else:
-            get_height_rel_highest = bitcoin_p2p.HeightTracker(bitcoind, factory, 5*net.SHARE_PERIOD*net.CHAIN_LENGTH/net.PARENT.BLOCK_PERIOD).get_height_rel_highest
+        get_height_rel_highest = yield height_tracker.get_height_rel_highest_func(bitcoind, factory, pre_current_work, net)
         
         def set_real_work2():
             best, desired = tracker.think(get_height_rel_highest, pre_current_work.value['previous_block'], pre_current_work.value['bits'])
@@ -296,13 +286,18 @@ def main(args, net, datadir_path, merged_urls, worker_endpoint):
                 print 'Sending %i shares to %s:%i' % (len(shares), peer.addr[0], peer.addr[1])
                 peer.sendShares(shares)
         
+        @deferral.retry('Error submitting block: (will retry)', 10, 10)
+        @defer.inlineCallbacks
+        def submit_block(block, ignore_failure):
+            success = yield bitcoind.rpc_getmemorypool(bitcoin_data.block_type.pack(block).encode('hex'))
+            success_expected = net.PARENT.POW_FUNC(bitcoin_data.block_header_type.pack(block['header'])) <= block['header']['bits'].target
+            if (not success and success_expected and not ignore_failure) or (success and not success_expected):
+                print >>sys.stderr, 'Block submittal result: %s Expected: %s' % (result, expected_result)
+        
         @tracker.verified.added.watch
         def _(share):
             if share.pow_hash <= share.header['bits'].target:
-                if factory.conn.value is not None:
-                    factory.conn.value.send_block(block=share.as_block(tracker))
-                else:
-                    print >>sys.stderr, 'No bitcoind connection when block submittal attempted! Erp!'
+                submit_block(share.as_block(tracker), ignore_failure=True)
                 print
                 print 'GOT BLOCK FROM PEER! Passing to bitcoind! %s bitcoin: %s%064x' % (p2pool_data.format_hash(share.hash), net.PARENT.BLOCK_EXPLORER_URL_PREFIX, share.header_hash)
                 print
@@ -448,23 +443,34 @@ def main(args, net, datadir_path, merged_urls, worker_endpoint):
             
             def preprocess_request(self, request):
                 user = request.getUser() if request.getUser() is not None else ''
-                pubkey_hash = my_pubkey_hash
-                max_target = 2**256 - 1
+                
+                desired_pseudoshare_target = None
+                if '+' in user:
+                    user, desired_pseudoshare_difficulty_str = user.rsplit('+', 1)
+                    try:
+                        desired_pseudoshare_target = bitcoin_data.difficulty_to_target(float(desired_pseudoshare_difficulty_str))
+                    except:
+                        pass
+                
+                desired_share_target = 2**256 - 1
                 if '/' in user:
                     user, min_diff_str = user.rsplit('/', 1)
                     try:
-                        max_target = bitcoin_data.difficulty_to_target(float(min_diff_str))
+                        desired_share_target = bitcoin_data.difficulty_to_target(float(min_diff_str))
                     except:
                         pass
-                try:
-                    pubkey_hash = bitcoin_data.address_to_pubkey_hash(user, net.PARENT)
-                except: # XXX blah
-                    pass
+                
                 if random.uniform(0, 100) < args.worker_fee:
                     pubkey_hash = my_pubkey_hash
-                return pubkey_hash, max_target
+                else:
+                    try:
+                        pubkey_hash = bitcoin_data.address_to_pubkey_hash(user, net.PARENT)
+                    except: # XXX blah
+                        pubkey_hash = my_pubkey_hash
+                
+                return pubkey_hash, desired_share_target, desired_pseudoshare_target
             
-            def get_work(self, pubkey_hash, max_target):
+            def get_work(self, pubkey_hash, desired_share_target, desired_pseudoshare_target):
                 if len(p2p_node.peers) == 0 and net.PERSIST:
                     raise jsonrpc.Error(-12345, u'p2pool is not connected to any peers')
                 if current_work.value['best_share_hash'] is None and net.PERSIST:
@@ -485,54 +491,34 @@ def main(args, net, datadir_path, merged_urls, worker_endpoint):
                     mm_data = ''
                     mm_later = []
                 
-                new = (tracker.shares[current_work.value['best_share_hash']].timestamp if current_work.value['best_share_hash'] is not None else time.time()) > net.SWITCH_TIME
-                
-                if new:
-                    share_info, generate_tx = p2pool_data.new_generate_transaction(
-                        tracker=tracker,
-                        share_data=dict(
-                            previous_share_hash=current_work.value['best_share_hash'],
-                            coinbase=(mm_data + current_work.value['coinbaseflags'])[:100],
-                            nonce=random.randrange(2**32),
-                            pubkey_hash=pubkey_hash,
-                            subsidy=current_work2.value['subsidy'],
-                            donation=math.perfect_round(65535*args.donation_percentage/100),
-                            stale_info=(lambda (orphans, doas), total, (orphans_recorded_in_chain, doas_recorded_in_chain):
-                                253 if orphans > orphans_recorded_in_chain else
-                                254 if doas > doas_recorded_in_chain else
-                                0
-                            )(*get_stale_counts()),
-                        ),
-                        block_target=current_work.value['bits'].target,
-                        desired_timestamp=int(time.time() - current_work2.value['clock_offset']),
-                        desired_target=max_target,
-                        net=net,
-                    )
-                else:
-                    share_info, generate_tx = p2pool_data.generate_transaction(
-                        tracker=tracker,
-                        share_data=dict(
-                            previous_share_hash=current_work.value['best_share_hash'],
-                            coinbase=(mm_data + current_work.value['coinbaseflags'])[:100],
-                            nonce=struct.pack('<Q', random.randrange(2**64)),
-                            new_script=bitcoin_data.pubkey_hash_to_script2(pubkey_hash),
-                            subsidy=current_work2.value['subsidy'],
-                            donation=math.perfect_round(65535*args.donation_percentage/100),
-                            stale_info=(lambda (orphans, doas), total, (orphans_recorded_in_chain, doas_recorded_in_chain):
-                                253 if orphans > orphans_recorded_in_chain else
-                                254 if doas > doas_recorded_in_chain else
-                                0
-                            )(*get_stale_counts()),
-                        ),
-                        block_target=current_work.value['bits'].target,
-                        desired_timestamp=int(time.time() - current_work2.value['clock_offset']),
-                        net=net,
-                    )
+                share_info, generate_tx = p2pool_data.generate_transaction(
+                    tracker=tracker,
+                    share_data=dict(
+                        previous_share_hash=current_work.value['best_share_hash'],
+                        coinbase=(mm_data + current_work.value['coinbaseflags'])[:100],
+                        nonce=random.randrange(2**32),
+                        pubkey_hash=pubkey_hash,
+                        subsidy=current_work2.value['subsidy'],
+                        donation=math.perfect_round(65535*args.donation_percentage/100),
+                        stale_info=(lambda (orphans, doas), total, (orphans_recorded_in_chain, doas_recorded_in_chain):
+                            253 if orphans > orphans_recorded_in_chain else
+                            254 if doas > doas_recorded_in_chain else
+                            0
+                        )(*get_stale_counts()),
+                    ),
+                    block_target=current_work.value['bits'].target,
+                    desired_timestamp=int(time.time() - current_work2.value['clock_offset']),
+                    desired_target=desired_share_target,
+                    net=net,
+                )
                 
                 target = net.PARENT.SANE_MAX_TARGET
-                if len(self.recent_shares_ts_work) == 50:
-                    hash_rate = sum(work for ts, work in self.recent_shares_ts_work)//(self.recent_shares_ts_work[-1][0] - self.recent_shares_ts_work[0][0])
-                    target = min(target, 2**256//(hash_rate))
+                if desired_pseudoshare_target is None:
+                    if len(self.recent_shares_ts_work) == 50:
+                        hash_rate = sum(work for ts, work in self.recent_shares_ts_work[1:])//(self.recent_shares_ts_work[-1][0] - self.recent_shares_ts_work[0][0])
+                        target = min(target, 2**256//hash_rate)
+                else:
+                    target = min(target, desired_pseudoshare_target)
                 target = max(target, share_info['bits'].target)
                 for aux_work in current_work.value['mm_chains'].itervalues():
                     target = max(target, aux_work['target'])
@@ -571,13 +557,7 @@ def main(args, net, datadir_path, merged_urls, worker_endpoint):
                     
                     try:
                         if pow_hash <= header['bits'].target or p2pool.DEBUG:
-                            @deferral.retry('Error submitting primary block: (will retry)', 10, 10)
-                            def submit_block():
-                                if factory.conn.value is None:
-                                    print >>sys.stderr, 'No bitcoind connection when block submittal attempted! %s%32x' % (net.PARENT.BLOCK_EXPLORER_URL_PREFIX, header_hash)
-                                    raise deferral.RetrySilentlyException()
-                                factory.conn.value.send_block(block=dict(header=header, txs=transactions))
-                            submit_block()
+                            submit_block(dict(header=header, txs=transactions), ignore_failure=False)
                             if pow_hash <= header['bits'].target:
                                 print
                                 print 'GOT BLOCK FROM MINER! Passing to bitcoind! %s%064x' % (net.PARENT.BLOCK_EXPLORER_URL_PREFIX, header_hash)
@@ -616,12 +596,10 @@ def main(args, net, datadir_path, merged_urls, worker_endpoint):
                             log.err(None, 'Error while processing merged mining POW:')
                     
                     if pow_hash <= share_info['bits'].target:
-                        if new:
-                            min_header = dict(header);del min_header['merkle_root']
-                            hash_link = p2pool_data.prefix_to_hash_link(packed_generate_tx[:-32-4], p2pool_data.gentx_before_refhash)
-                            share = p2pool_data.NewShare(net, None, min_header, share_info, hash_link=hash_link, merkle_branch=merkle_branch, other_txs=transactions[1:] if pow_hash <= header['bits'].target else None)
-                        else:
-                            share = p2pool_data.Share(net, None, header, share_info, merkle_branch=merkle_branch, other_txs=transactions[1:] if pow_hash <= header['bits'].target else None)
+                        min_header = dict(header);del min_header['merkle_root']
+                        hash_link = p2pool_data.prefix_to_hash_link(packed_generate_tx[:-32-4], p2pool_data.gentx_before_refhash)
+                        share = p2pool_data.Share(net, None, min_header, share_info, hash_link=hash_link, merkle_branch=merkle_branch, other_txs=transactions[1:] if pow_hash <= header['bits'].target else None)
+                        
                         print 'GOT SHARE! %s %s prev %s age %.2fs%s' % (
                             request.getUser(),
                             p2pool_data.format_hash(share.hash),
@@ -646,22 +624,21 @@ def main(args, net, datadir_path, merged_urls, worker_endpoint):
                         except:
                             log.err(None, 'Error forwarding block solution:')
                     
-                    if pow_hash <= target and header_hash not in received_header_hashes:
+                    if pow_hash > target:
+                        print 'Worker %s submitted share with hash > target:' % (request.getUser(),)
+                        print '    Hash:   %56x' % (pow_hash,)
+                        print '    Target: %56x' % (target,)
+                    elif header_hash in received_header_hashes:
+                        print >>sys.stderr, 'Worker %s @ %s submitted share more than once!' % (request.getUser(), request.getClientIP())
+                    else:
+                        received_header_hashes.add(header_hash)
+                        
                         pseudoshare_received.happened(bitcoin_data.target_to_average_attempts(target), not on_time, request.getUser() if request.getPassword() == vip_pass else None)
                         self.recent_shares_ts_work.append((time.time(), bitcoin_data.target_to_average_attempts(target)))
                         while len(self.recent_shares_ts_work) > 50:
                             self.recent_shares_ts_work.pop(0)
                         local_rate_monitor.add_datum(dict(work=bitcoin_data.target_to_average_attempts(target), dead=not on_time, user=request.getUser()))
                     
-                    if header_hash in received_header_hashes:
-                        print >>sys.stderr, 'Worker %s @ %s submitted share more than once!' % (request.getUser(), request.getClientIP())
-                    received_header_hashes.add(header_hash)
-                    
-                    if pow_hash > target:
-                        print 'Worker %s submitted share with hash > target:' % (request.getUser(),)
-                        print '    Hash:   %56x' % (pow_hash,)
-                        print '    Target: %56x' % (target,)
-                    
                     return on_time
                 
                 return ba, got_response
@@ -726,7 +703,7 @@ def main(args, net, datadir_path, merged_urls, worker_endpoint):
                     if share.pow_hash <= share.header['bits'].target and share.header_hash not in self.announced_hashes and abs(share.timestamp - time.time()) < 10*60:
                         self.announced_hashes.add(share.header_hash)
                         message = '\x02%s BLOCK FOUND by %s! %s%064x' % (net.NAME.upper(), bitcoin_data.script2_to_address(share.new_script, net.PARENT), net.PARENT.BLOCK_EXPLORER_URL_PREFIX, share.header_hash)
-                        self.delayed_messages[message] = reactor.callLater(random.expovariate(1/5), lambda: (self.say(self.channel, message), self.delayed_messages.pop(message)))
+                        self.delayed_messages[message] = reactor.callLater(random.expovariate(1/60), lambda: (self.say(self.channel, message), self.delayed_messages.pop(message)))
                 def connectionLost(self, reason):
                     tracker.verified.added.unwatch(self.watch_id)
                     print 'IRC connection lost:', reason.getErrorMessage()
@@ -928,6 +905,8 @@ def run():
             ]:
                 if getattr(args, var_name) is None and cp.has_option('x', conf_name):
                     setattr(args, var_name, var_type(cp.get('x', conf_name)))
+        if args.bitcoind_rpc_password is None:
+            parser.error('''Bitcoin configuration file didn't contain an rpcpassword= line! Add one!''')
     
     if args.bitcoind_rpc_username is None:
         args.bitcoind_rpc_username = ''