1 # Eloipool - Python Bitcoin pool server
2 # Copyright (C) 2011-2012 Luke Dashjr <luke-jr+eloipool@utopios.org>
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.
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.
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/>.
17 from hashlib import sha256
21 from struct import unpack
27 return 'Y' if b else 'N'
35 def target2pdiff(target):
38 pdiff = round(2**(224 - log(target, 2)), 8)
39 return _maybe_int(pdiff)
41 bdiff1target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
43 def target2bdiff(target):
44 bdiff = bdiff1target / target
45 return _maybe_int(bdiff)
47 class shareLogFormatter:
48 _re_x = re.compile(r'^\s*(\w+)\s*(?:\(\s*(.*?)\s*\))?\s*$')
50 def __init__(self, *a, **ka):
51 self._p = self.parse(*a, **ka)
53 # NOTE: This only works for psf='%s' (default)
54 def formatShare(self, *a, **ka):
55 (stmt, params) = self.applyToShare(*a, **ka)
58 def applyToShare(self, share):
59 (stmt, stmtf) = self._p
62 params.append(f(share))
63 params = tuple(params)
67 def parse(self, stmt, psf = '%s'):
68 fmt = string.Formatter()
69 pstmt = tuple(fmt.parse(stmt))
73 for (lit, field, fmtspec, conv) in pstmt:
77 f = self.get_field(field)
84 def get_field(self, field):
85 m = self._re_x.match(field)
87 if m.group(2) is None:
89 return lambda s: s.get(field, None)
93 sf = self.get_field(m.group(2))
94 gfm = 'get_field_%s' % (fn,)
95 if hasattr(self, gfm):
96 return getattr(self, gfm)(sf)
98 return self._get_field_auto(f, sf)
99 raise ValueError('Failed to parse field: %s' % (field,))
102 def _get_field_auto(self, f, subfunc):
103 return lambda s: f(subfunc(s))
106 def get_field_not(self, subfunc):
107 return lambda s: not subfunc(s)
110 def get_field_Q(self, subfunc):
111 return lambda s: subfunc(s) or '?'
114 def get_field_dash(self, subfunc):
115 return lambda s: subfunc(s) or '-'
118 return sha256(sha256(b).digest()).digest()
122 for i in range(0, len(b), 4):
123 o += b[i + 3:i - 1 if i else None:-1]
126 def Bits2Target(bits):
127 return unpack('<L', bits[:3] + b'\0')[0] * 2**(8*(bits[3] - 3))
130 n = unpack('<QQQQ', h)
131 n = (n[3] << 192) | (n[2] << 128) | (n[1] << 64) | n[0]
135 n = unpack('>QQQQ', h)
136 n = (n[0] << 192) | (n[1] << 128) | (n[2] << 64) | n[3]
139 def tryErr(func, *a, **kw):
140 IE = kw.pop('IgnoredExceptions', BaseException)
141 logger = kw.pop('Logger', None)
142 emsg = kw.pop('ErrorMsg', None)
144 return func(*a, **kw)
147 emsg = "%s\n" % (emsg,) if emsg else ""
148 emsg += traceback.format_exc()
152 class RejectedShare(ValueError):
155 PendingUpstream = object()
165 def _build_heap(self):
166 newheap = list((v[0], id(o), o) for o, v in self._dict.items())
167 heapq.heapify(newheap)
172 (t, k, o) = self._heap[0]
175 heapq.heappop(self._heap)
180 (t, k, o) = heapq.heappop(self._heap)
186 def __setitem__(self, o, t):
188 self._dict[o] = (t, o)
189 if len(self._heap) / 2 > len(self._dict):
192 heapq.heappush(self._heap, (t, k, o))
194 def __contains__(self, o):
195 return o in self._dict
197 def __getitem__(self, o):
198 return self._dict[o][0]
200 def __delitem__(self, o):
202 if len(self._dict) < 2:
206 return len(self._dict)
211 def __exit__(self, *a):
213 WithNoop = WithNoop()
216 from collections import deque
220 class _UniqueSessionIdManager:
221 def __init__(self, size = 4, defaultDelay = 120):
223 self._NextID_Lock = threading.Lock()
224 self._FreeIDs = deque()
226 self._max = (0x100 ** size) - 1
227 self._defaultDelay = defaultDelay
228 self._schPut = ScheduleDict()
229 self._schPut_Lock = threading.Lock()
234 def put(self, sid, delay = False, now = None):
236 return self._FreeIDs.append(sid)
239 delay = self._defaultDelay
242 with self._schPut_Lock:
243 self._schPut[sid] = now + delay
245 def get(self, desired = None, now = None):
247 return self._FreeIDs.popleft()
251 # Check delayed-free for one
254 with self._schPut_Lock:
255 if len(self._schPut) and self._schPut.nextTime() <= now:
256 sid = self._schPut.shift()
257 while len(self._schPut) and self._schPut.nextTime() <= now:
258 self.put(self._schPut.shift())
261 # If none free, make a new one
262 with self._NextID_Lock:
264 self._NextID = sid + 1
268 # TODO: Maybe steal an about-to-be-freed SID in the worst case scenario?
270 raise IndexError('Ran out of session ids')
272 # NOTE: Will steal a pending-free sid
273 def getSpecific(self, desired):
275 self._FreeIDs.remove(desired)
280 # FIXME: relies on id(number) == id(number)
281 with self._schPut_Lock:
282 if desired in self._schPut:
283 del self._schPut[desired]
286 # NOTE: Generated growth is limited to avoid memory exhaustion exploits
287 with self._NextID_Lock:
288 NextID = self._NextID
289 if desired >= NextID and desired <= min(self._max, NextID + 0x10000 - len(self._FreeIDs)):
290 # NOTE: Incrementing _NextID up front in case of exception
291 self._NextID = desired + 1
292 for i in range(NextID, desired):
296 raise KeyError('Session id %u not available' % (desired,))
298 UniqueSessionIdManager = _UniqueSessionIdManager()