fixed jsonrpc import missing in bitcoin.height_tracker, which prevented a useful...
[p2pool.git] / p2pool / bitcoin / height_tracker.py
1 from twisted.internet import defer, task
2 from twisted.python import log
3
4 from p2pool.bitcoin import data as bitcoin_data
5 from p2pool.util import deferral, forest, jsonrpc, variable
6
7 class HeaderWrapper(object):
8     __slots__ = 'hash previous_hash'.split(' ')
9     
10     @classmethod
11     def from_header(cls, header):
12         return cls(bitcoin_data.hash256(bitcoin_data.block_header_type.pack(header)), header['previous_block'])
13     
14     def __init__(self, hash, previous_hash):
15         self.hash, self.previous_hash = hash, previous_hash
16
17 class HeightTracker(object):
18     '''Point this at a factory and let it take care of getting block heights'''
19     
20     def __init__(self, best_block_func, factory, backlog_needed):
21         self._best_block_func = best_block_func
22         self._factory = factory
23         self._backlog_needed = backlog_needed
24         
25         self._tracker = forest.Tracker()
26         
27         self._watch1 = self._factory.new_headers.watch(self._heard_headers)
28         self._watch2 = self._factory.new_block.watch(self._request)
29         
30         self._requested = set()
31         self._clear_task = task.LoopingCall(self._requested.clear)
32         self._clear_task.start(60)
33         
34         self._last_notified_size = 0
35         
36         self.updated = variable.Event()
37         
38         self._think_task = task.LoopingCall(self._think)
39         self._think_task.start(15)
40         self._think2_task = task.LoopingCall(self._think2)
41         self._think2_task.start(15)
42     
43     def _think(self):
44         try:
45             highest_head = max(self._tracker.heads, key=lambda h: self._tracker.get_height_and_last(h)[0]) if self._tracker.heads else None
46             if highest_head is None:
47                 return # wait for think2
48             height, last = self._tracker.get_height_and_last(highest_head)
49             if height < self._backlog_needed:
50                 self._request(last)
51         except:
52             log.err(None, 'Error in HeightTracker._think:')
53     
54     def _think2(self):
55         self._request(self._best_block_func())
56     
57     def _heard_headers(self, headers):
58         changed = False
59         for header in headers:
60             hw = HeaderWrapper.from_header(header)
61             if hw.hash in self._tracker.shares:
62                 continue
63             changed = True
64             self._tracker.add(hw)
65         if changed:
66             self.updated.happened()
67         self._think()
68         
69         if len(self._tracker.shares) >= self._last_notified_size + 100:
70             print 'Have %i/%i block headers' % (len(self._tracker.shares), self._backlog_needed)
71             self._last_notified_size = len(self._tracker.shares)
72     
73     @defer.inlineCallbacks
74     def _request(self, last):
75         if last in self._tracker.shares:
76             return
77         if last in self._requested:
78             return
79         self._requested.add(last)
80         (yield self._factory.getProtocol()).send_getheaders(version=1, have=[], last=last)
81     
82     def get_height_rel_highest(self, block_hash):
83         # callers: highest height can change during yields!
84         best_height, best_last = self._tracker.get_height_and_last(self._best_block_func())
85         height, last = self._tracker.get_height_and_last(block_hash)
86         if last != best_last:
87             return -1000000000 # XXX hack
88         return height - best_height
89
90 @defer.inlineCallbacks
91 def get_height_rel_highest_func(bitcoind, factory, best_block_func, net):
92     if '\ngetblock ' in (yield deferral.retry()(bitcoind.rpc_help)()):
93         @deferral.DeferredCacher
94         @defer.inlineCallbacks
95         def height_cacher(block_hash):
96             try:
97                 x = yield bitcoind.rpc_getblock('%x' % (block_hash,))
98             except jsonrpc.Error, e:
99                 if e.code == -5 and not p2pool.DEBUG:
100                     raise deferral.RetrySilentlyException()
101                 raise
102             defer.returnValue(x['blockcount'] if 'blockcount' in x else x['height'])
103         best_height_cached = variable.Variable((yield deferral.retry()(height_cacher)(best_block_func())))
104         def get_height_rel_highest(block_hash):
105             this_height = height_cacher.call_now(block_hash, 0)
106             best_height = height_cacher.call_now(best_block_func(), 0)
107             best_height_cached.set(max(best_height_cached.value, this_height, best_height))
108             return this_height - best_height_cached.value
109     else:
110         get_height_rel_highest = HeightTracker(best_block_func, factory, 5*net.SHARE_PERIOD*net.CHAIN_LENGTH/net.PARENT.BLOCK_PERIOD).get_height_rel_highest
111     defer.returnValue(get_height_rel_highest)