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