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