Initial commit
[stratum-mining.git] / mining / service.py
1 import binascii
2 from twisted.internet import defer
3
4 from stratum.services import GenericService, admin
5 from stratum.custom_exceptions import ServiceException
6 from stratum.pubsub import Pubsub
7 from interfaces import Interfaces
8 from subscription import MiningSubscription
9
10 import stratum.logger
11 log = stratum.logger.get_logger('mining')
12
13 class SubmitException(ServiceException):
14     pass
15                 
16 class MiningService(GenericService):
17     '''This service provides public API for Stratum mining proxy
18     or any Stratum-compatible miner software.
19     
20     Warning - any callable argument of this class will be propagated
21     over Stratum protocol for public audience!'''
22     
23     service_type = 'mining'
24     service_vendor = 'stratum'
25     is_default = True
26     
27     @admin
28     def update_block(self):
29         '''Connect this RPC call to 'bitcoind -blocknotify' for 
30         instant notification about new block on the network.
31         See blocknotify.sh in /scripts/ for more info.'''
32         Interfaces.template_registry.update_block()
33         return True 
34     
35     def authorize(self, worker_name, worker_password):
36         '''Let authorize worker on this connection.'''
37         return Interfaces.worker_manager.authorize(worker_name, worker_password)
38         
39     def subscribe(self):
40         '''Subscribe for receiving mining jobs. This will
41         return subscription details, extranonce1_hex and extranonce2_size'''
42         
43         extranonce1 = Interfaces.template_registry.get_new_extranonce1()
44         extranonce2_size = Interfaces.template_registry.extranonce2_size
45
46         session = self.connection_ref().get_session()
47         session['extranonce1'] = extranonce1
48         session['difficulty'] = 1 # Following protocol specs, default diff is 1
49
50         extranonce1_hex = binascii.hexlify(extranonce1)
51             
52         return Pubsub.subscribe(self.connection_ref(), MiningSubscription()) + (extranonce1_hex, extranonce2_size)
53     
54     '''    
55     def submit(self, worker_name, job_id, extranonce2, ntime, nonce):
56         import time
57         start = time.time()
58         
59         for x in range(100):
60             try:
61                 ret = self.submit2(worker_name, job_id, extranonce2, ntime, nonce)
62             except:
63                 pass
64             
65         log.info("LEN %.03f" % (time.time() - start))
66         return ret
67     '''
68     
69     def submit(self, worker_name, job_id, extranonce2, ntime, nonce):
70         '''Try to solve block candidate using given parameters.'''
71         
72         session = self.connection_ref().get_session()
73
74         # Check if extranonce1 is in connection session
75         extranonce1_bin = session.get('extranonce1', None)
76         if not extranonce1_bin:
77             raise SubmitException("Connection is not subscribed for mining")
78         
79         difficulty = session['difficulty']
80
81         # This checks if submitted share meet all requirements
82         # and it is valid proof of work.
83         (is_valid, reason, block_header, block_hash) = Interfaces.template_registry.submit_share(job_id,
84                                                 worker_name, extranonce1_bin, extranonce2, ntime, nonce, difficulty,
85                                                 Interfaces.share_manager.on_submit_block)
86         
87         if block_header != None:
88             # block header is missing when template registry was unable to build it
89             # from given parameters. Client side is probably broken, storing such
90             # submit don't have any sense.                   
91             Interfaces.share_manager.on_submit_share(worker_name, block_header, block_hash, difficulty,
92                                               Interfaces.timestamper.time(), is_valid)
93         
94         if not is_valid:
95             raise SubmitException(reason)
96
97         return True
98             
99     # Service documentation for remote discovery
100     update_block.help_text = "Notify Stratum server about new block on the network."
101     update_block.params = [('password', 'string', 'Administrator password'),]
102     
103     authorize.help_text = "Authorize worker for submitting shares on this connection."
104     authorize.params = [('worker_name', 'string', 'Name of the worker, usually in the form of user_login.worker_id.'),
105                         ('worker_password', 'string', 'Worker password'),]
106     
107     subscribe.help_text = "Subscribes current connection for receiving new mining jobs."
108     subscribe.params = []
109     
110     submit.help_text = "Submit solved share back to the server. Excessive sending of invalid shares "\
111                        "or shares above indicated target (see Stratum mining docs for set_target()) may lead "\
112                        "to temporary or permanent ban of user,worker or IP address."
113     submit.params = [('worker_name', 'string', 'Name of the worker, usually in the form of user_login.worker_id.'),
114                      ('job_id', 'string', 'ID of job (received by mining.notify) which the current solution is based on.'),
115                      ('extranonce2', 'string', 'hex-encoded big-endian extranonce2, length depends on extranonce2_size from mining.notify.'),
116                      ('ntime', 'string', 'UNIX timestamp (32bit integer, big-endian, hex-encoded), must be >= ntime provided by mining,notify and <= current time'),
117                      ('nonce', 'string', '32bit integer, hex-encoded, big-endian'),]
118