Bugfix: Stratum: Replies should not be sent if request id is null
[bitcoin:eloipool.git] / util.py
1 # Eloipool - Python Bitcoin pool server
2 # Copyright (C) 2011-2012  Luke Dashjr <luke-jr+eloipool@utopios.org>
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as
6 # published by the Free Software Foundation, either version 3 of the
7 # License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 from hashlib import sha256
18 from math import log
19 import re
20 import string
21 from struct import unpack
22 import traceback
23
24 def YN(b):
25         if b is None:
26                 return None
27         return 'Y' if b else 'N'
28
29 def target2pdiff(target):
30         if target is None:
31                 return None
32         pdiff = round(2**(224 - log(target, 2)), 8)
33         pdiff_int = int(pdiff)
34         if pdiff == pdiff_int:
35                 pdiff = pdiff_int
36         return pdiff
37
38 class shareLogFormatter:
39         _re_x = re.compile(r'^\s*(\w+)\s*(?:\(\s*(.*?)\s*\))?\s*$')
40         
41         def __init__(self, *a, **ka):
42                 self._p = self.parse(*a, **ka)
43         
44         # NOTE: This only works for psf='%s' (default)
45         def formatShare(self, *a, **ka):
46                 (stmt, params) = self.applyToShare(*a, **ka)
47                 return stmt % params
48         
49         def applyToShare(self, share):
50                 (stmt, stmtf) = self._p
51                 params = []
52                 for f in stmtf:
53                         params.append(f(share))
54                 params = tuple(params)
55                 return (stmt, params)
56         
57         @classmethod
58         def parse(self, stmt, psf = '%s'):
59                 fmt = string.Formatter()
60                 pstmt = tuple(fmt.parse(stmt))
61                 
62                 stmt = ''
63                 fmt = []
64                 for (lit, field, fmtspec, conv) in pstmt:
65                         stmt += lit
66                         if not field:
67                                 continue
68                         f = self.get_field(field)
69                         fmt.append(f)
70                         stmt += psf
71                 fmt = tuple(fmt)
72                 return (stmt, fmt)
73         
74         @classmethod
75         def get_field(self, field):
76                 m = self._re_x.match(field)
77                 if m:
78                         if m.group(2) is None:
79                                 # identifier
80                                 return lambda s: s.get(field, None)
81                         else:
82                                 # function
83                                 fn = m.group(1)
84                                 sf = self.get_field(m.group(2))
85                                 return getattr(self, 'get_field_%s' % (fn,))(sf)
86                 raise ValueError('Failed to parse field: %s' % (field,))
87         
88         @classmethod
89         def get_field_not(self, subfunc):
90                 return lambda s: not subfunc(s)
91         
92         @classmethod
93         def get_field_Q(self, subfunc):
94                 return lambda s: subfunc(s) or '?'
95         
96         @classmethod
97         def get_field_dash(self, subfunc):
98                 return lambda s: subfunc(s) or '-'
99         
100         @classmethod
101         def get_field_YN(self, subfunc):
102                 return lambda s: YN(subfunc(s))
103         
104         @classmethod
105         def get_field_target2pdiff(self, subfunc):
106                 return lambda s: target2pdiff(subfunc(s))
107
108 def dblsha(b):
109         return sha256(sha256(b).digest()).digest()
110
111 def swap32(b):
112         o = b''
113         for i in range(0, len(b), 4):
114                 o += b[i + 3:i - 1 if i else None:-1]
115         return o
116
117 def Bits2Target(bits):
118         return unpack('<L', bits[:3] + b'\0')[0] * 2**(8*(bits[3] - 3))
119
120 def hash2int(h):
121         n = unpack('<QQQQ', h)
122         n = (n[3] << 192) | (n[2] << 128) | (n[1] << 64) | n[0]
123         return n
124
125 def tryErr(func, *a, **kw):
126         IE = kw.pop('IgnoredExceptions', BaseException)
127         logger = kw.pop('Logger', None)
128         emsg = kw.pop('ErrorMsg', None)
129         try:
130                 return func(*a, **kw)
131         except IE:
132                 if logger:
133                         emsg = "%s\n" % (emsg,) if emsg else ""
134                         emsg += traceback.format_exc()
135                         logger.error(emsg)
136                 return None
137
138 class RejectedShare(ValueError):
139         pass
140
141
142 import heapq
143
144 class ScheduleDict:
145         def __init__(self):
146                 self._dict = {}
147                 self._build_heap()
148         
149         def _build_heap(self):
150                 newheap = list((v[0], k, v[1]) for k, v in self._dict.items())
151                 heapq.heapify(newheap)
152                 self._heap = newheap
153         
154         def nextTime(self):
155                 while True:
156                         (t, k, o) = self._heap[0]
157                         if k in self._dict:
158                                 break
159                         heapq.heappop(self._heap)
160                 return t
161         
162         def shift(self):
163                 while True:
164                         (t, k, o) = heapq.heappop(self._heap)
165                         if k in self._dict:
166                                 break
167                 del self._dict[k]
168                 return o
169         
170         def __setitem__(self, o, t):
171                 k = id(o)
172                 self._dict[k] = (t, o)
173                 if len(self._heap) / 2 > len(self._dict):
174                         self._build_heap()
175                 else:
176                         heapq.heappush(self._heap, (t, k, o))
177         
178         def __getitem__(self, o):
179                 return self._dict[id(o)][0]
180         
181         def __delitem__(self, o):
182                 del self._dict[id(o)]
183                 if len(self._dict) < 2:
184                         self._build_heap()
185         
186         def __len__(self):
187                 return len(self._dict)