Add Litecoin network.
[p2pool.git] / p2pool / main.py
index bb78ce2..bf2ce33 100644 (file)
@@ -15,7 +15,7 @@ import json
 import signal
 import traceback
 
-from twisted.internet import defer, reactor, task, threads
+from twisted.internet import defer, reactor, task
 from twisted.web import server, resource
 from twisted.python import log
 from nattraverso import portmapper, ipdiscover
@@ -37,7 +37,7 @@ def getwork(bitcoind, ht, net):
             transactions=[bitcoin.data.tx_type.unpack(x.decode('hex')) for x in work['transactions']],
             subsidy=work['coinbasevalue'],
             time=work['time'],
-            target=bitcoin.data.FloatingInteger(work['bits']),
+            target=bitcoin.data.FloatingIntegerType().unpack(work['bits'].decode('hex')[::-1]) if isinstance(work['bits'], (str, unicode)) else bitcoin.data.FloatingInteger(work['bits']),
         ))
     except jsonrpc.Error, e:
         if e.code != -32601:
@@ -78,11 +78,14 @@ def get_payout_script2(bitcoind, net):
 @defer.inlineCallbacks
 def main(args):
     try:
-        if args.charts:
-            from . import draw
-        
         print 'p2pool (version %s)' % (p2pool_init.__version__,)
         print
+        try:
+            from . import draw
+        except ImportError:
+            draw = None
+            print "Install Pygame and PIL to enable visualizations! Visualizations disabled."
+            print
         
         # connect to bitcoind over JSON-RPC and do initial getwork
         url = 'http://%s:%i/' % (args.bitcoind_address, args.bitcoind_rpc_port)
@@ -168,6 +171,7 @@ def main(args):
                 previous_block=work['previous_block_hash'],
                 target=work['target'],
                 best_share_hash=current_work.value['best_share_hash'] if current_work.value is not None else None,
+                aux_work=current_work.value['aux_work'] if current_work.value is not None else None,
             ))
             current_work2.set(dict(
                 transactions=work['transactions'],
@@ -219,6 +223,24 @@ def main(args):
         print '    ...success!'
         print
         
+        @defer.inlineCallbacks
+        def set_merged_work():
+            if not args.merged_url:
+                return
+            while True:
+                merged = jsonrpc.Proxy(args.merged_url, (args.merged_userpass,))
+                auxblock = yield deferral.retry('Error while calling merged getauxblock:', 1)(merged.rpc_getauxblock)()
+                x = dict(current_work.value)
+                x['aux_work'] = dict(
+                    hash=int(auxblock['hash'], 16),
+                    target=bitcoin.data.HashType().unpack(auxblock['target'].decode('hex')),
+                    chain_id=auxblock['chainid'],
+                )
+                #print x['aux_work']
+                current_work.set(x)
+                yield deferral.sleep(1)
+        set_merged_work()
+        
         start_time = time.time() - current_work2.value['clock_offset']
         
         # setup p2p logic and join p2pool network
@@ -373,7 +395,7 @@ def main(args):
         # setup worker logic
         
         merkle_root_to_transactions = expiring_dict.ExpiringDict(300)
-        run_identifier = struct.pack('<Q', random.randrange(2**64))
+        run_identifier = struct.pack('<I', random.randrange(2**32))
         
         share_counter = skiplists.CountsSkipList(tracker, run_identifier)
         removed_unstales = set()
@@ -402,6 +424,11 @@ def main(args):
             if time.time() > current_work2.value['last_update'] + 60:
                 raise jsonrpc.Error(-12345, u'lost contact with bitcoind')
             
+            if state['aux_work'] is not None:
+                aux_str = '\xfa\xbemm' + bitcoin.data.HashType().pack(state['aux_work']['hash'])[::-1] + struct.pack('<ii', 1, 0)
+            else:
+                aux_str = ''
+            
             # XXX assuming generate_tx is smallish here..
             def get_stale_frac():
                 shares, stale_shares = get_share_counts()
@@ -415,7 +442,7 @@ def main(args):
                 previous_share_hash=state['best_share_hash'],
                 new_script=payout_script,
                 subsidy=subsidy,
-                nonce=run_identifier + struct.pack('<Q', random.randrange(2**64)) + get_stale_frac(),
+                nonce=run_identifier + struct.pack('<H', random.randrange(2**16)) + get_stale_frac() + aux_str,
                 block_target=state['target'],
                 net=args.net,
             )
@@ -460,6 +487,27 @@ def main(args):
                         print
                         print 'GOT BLOCK! Passing to bitcoind! bitcoin: %x' % (hash_,)
                         print
+                try:
+                    aux_pow = dict(
+                        merkle_tx=dict(
+                            tx=transactions[0],
+                            block_hash=hash_,
+                            merkle_branch=[x['hash'] for x in p2pool.calculate_merkle_branch(transactions, 0)],
+                            index=0,
+                        ),
+                        merkle_branch=[],
+                        index=0,
+                        parent_block_header=header,
+                    )
+                    
+                    a, b = transactions[0]['tx_ins'][0]['script'][-32-8:-8].encode('hex'), bitcoin.data.aux_pow_type.pack(aux_pow).encode('hex')
+                    #print a, b
+                    merged = jsonrpc.Proxy(args.merged_url, (args.merged_userpass,))
+                    def _(res):
+                        print "MERGED RESULT:", res
+                    merged.rpc_getauxblock(a, b).addBoth(_)
+                except:
+                    log.err(None, 'Error while processing merged mining POW:')
                 target = p2pool.coinbase_type.unpack(transactions[0]['tx_ins'][0]['script'])['share_data']['target']
                 if hash_ > target:
                     print 'Worker submitted share with hash > target:\nhash  : %x\ntarget: %x' % (hash_, target)
@@ -506,8 +554,8 @@ def main(args):
         
         web_root.putChild('rate', WebInterface(get_rate, 'application/json'))
         web_root.putChild('users', WebInterface(get_users, 'application/json'))
-        web_root.putChild('fee', WebInterface(lambda: json.dumps(arg.worker_fee), 'application/json'))
-        if args.charts:
+        web_root.putChild('fee', WebInterface(lambda: json.dumps(args.worker_fee), 'application/json'))
+        if draw is not None:
             web_root.putChild('chain_img', WebInterface(lambda: draw.get(tracker, current_work.value['best_share_hash']), 'image/png'))
         
         reactor.listenTCP(args.worker_port, server.Site(web_root))
@@ -600,8 +648,8 @@ def main(args):
                                 print '    Own:', stale_shares/shares
                                 if med < .99:
                                     print '    Own efficiency: %.02f%%' % (100*(1 - stale_shares/shares)/(1 - med),)
-                            
-                            
+            
+            
             except:
                 log.err()
     except:
@@ -639,9 +687,11 @@ def run():
             
             # return the modified argument list
             return new_arg_strings
+        
+        def convert_arg_line_to_args(self, arg_line):
+            return [arg for arg in arg_line.split() if arg.strip()]
     
     parser = FixedArgumentParser(description='p2pool (version %s)' % (p2pool_init.__version__,), fromfile_prefix_chars='@')
-    parser.convert_arg_line_to_args = lambda arg_line: (arg for arg in arg_line.split() if arg.strip())
     parser.add_argument('--version', action='version', version=p2pool_init.__version__)
     parser.add_argument('--net',
         help='use specified network (default: bitcoin)',
@@ -655,12 +705,15 @@ def run():
     parser.add_argument('-a', '--address',
         help='generate to this address (defaults to requesting one from bitcoind)',
         type=str, action='store', default=None, dest='address')
-    parser.add_argument('--charts',
-        help='generate charts on the web interface (requires PIL and pygame)',
-        action='store_const', const=True, default=False, dest='charts')
     parser.add_argument('--logfile',
         help='''log to specific file (defaults to <network_name>.log in run_p2pool.py's directory)''',
         type=str, action='store', default=None, dest='logfile')
+    parser.add_argument('--merged-url',
+        help='call getauxblock on this url to get work for merged mining',
+        type=str, action='store', default=None, dest='merged_url')
+    parser.add_argument('--merged-userpass',
+        help='merge daemon user and password, separated by a colon. Example: ncuser:ncpass',
+        type=str, action='store', default=None, dest='merged_userpass')
     
     p2pool_group = parser.add_argument_group('p2pool interface')
     p2pool_group.add_argument('--p2pool-port', metavar='PORT',
@@ -678,7 +731,7 @@ def run():
     
     worker_group = parser.add_argument_group('worker interface')
     worker_group.add_argument('-w', '--worker-port', metavar='PORT',
-        help='listen on PORT for RPC connections from miners asking for work and providing responses (default: bitcoin: 9332 namecoin: 9331 ixcoin: 9330 i0coin: 9329, +10000 for testnets)',
+        help='listen on PORT for RPC connections from miners asking for work and providing responses (default: bitcoin: 9332 namecoin: 9331 ixcoin: 9330 i0coin: 9329 solidcoin: 9328 litecoin: 9327, +10000 for testnets)',
         type=int, action='store', default=None, dest='worker_port')
     worker_group.add_argument('-f', '--fee', metavar='FEE_PERCENTAGE',
         help='''charge workers mining to their own bitcoin address (by setting their miner's username to a bitcoin address) this percentage fee to mine on your p2pool instance. Amount displayed at http://127.0.0.1:9332/fee . default: 0''',
@@ -689,10 +742,10 @@ def run():
         help='connect to a bitcoind at this address (default: 127.0.0.1)',
         type=str, action='store', default='127.0.0.1', dest='bitcoind_address')
     bitcoind_group.add_argument('--bitcoind-rpc-port', metavar='BITCOIND_RPC_PORT',
-        help='connect to a bitcoind at this port over the RPC interface - used to get the current highest block via getwork (default: 8332, 8338 for ixcoin)',
+        help='connect to a bitcoind at this port over the RPC interface - used to get the current highest block via getwork (default: 8332 ixcoin: 8338 i0coin: 7332 litecoin: 9332)',
         type=int, action='store', default=None, dest='bitcoind_rpc_port')
     bitcoind_group.add_argument('--bitcoind-p2p-port', metavar='BITCOIND_P2P_PORT',
-        help='connect to a bitcoind at this port over the p2p interface - used to submit blocks and get the pubkey to generate to via an IP transaction (default: 8333 normally. 18333 for testnet)',
+        help='connect to a bitcoind at this port over the p2p interface - used to submit blocks and get the pubkey to generate to via an IP transaction (default: 8333 namecoin: 8334 ixcoin: 8337 i0coin: 7333 solidcoin: 7555 litecoin: 9333, +10000 for testnets)',
         type=int, action='store', default=None, dest='bitcoind_p2p_port')
     
     bitcoind_group.add_argument(metavar='BITCOIND_RPCUSER',
@@ -708,7 +761,7 @@ def run():
         p2pool_init.DEBUG = True
     
     if args.logfile is None:
-       args.logfile = os.path.join(os.path.dirname(sys.argv[0]), args.net_name + ('_testnet' if args.testnet else '') + '.log')
+        args.logfile = os.path.join(os.path.dirname(sys.argv[0]), args.net_name + ('_testnet' if args.testnet else '') + '.log')
     
     class LogFile(object):
         def __init__(self, filename):
@@ -788,9 +841,12 @@ def run():
         try:
             args.pubkey_hash = bitcoin.data.address_to_pubkey_hash(args.address, args.net)
         except Exception, e:
-            raise ValueError('error parsing address: ' + repr(e))
+            parser.error('error parsing address: ' + repr(e))
     else:
         args.pubkey_hash = None
     
+    if (args.merged_url is None) ^ (args.merged_userpass is None):
+        parser.error('must specify --merged-url and --merged-userpass')
+    
     reactor.callWhenRunning(main, args)
     reactor.run()