1 from twisted.internet import defer, task
2 from twisted.python import log
5 from p2pool.bitcoin import data as bitcoin_data
6 from p2pool.util import deferral, forest, jsonrpc, variable
8 class HeaderWrapper(object):
9 __slots__ = 'hash previous_hash'.split(' ')
12 def from_header(cls, header):
13 return cls(bitcoin_data.hash256(bitcoin_data.block_header_type.pack(header)), header['previous_block'])
15 def __init__(self, hash, previous_hash):
16 self.hash, self.previous_hash = hash, previous_hash
18 class HeightTracker(object):
19 '''Point this at a factory and let it take care of getting block heights'''
21 def __init__(self, best_block_func, factory, backlog_needed):
22 self._best_block_func = best_block_func
23 self._factory = factory
24 self._backlog_needed = backlog_needed
26 self._tracker = forest.Tracker()
28 self._watch1 = self._factory.new_headers.watch(self._heard_headers)
29 self._watch2 = self._factory.new_block.watch(self._request)
31 self._requested = set()
32 self._clear_task = task.LoopingCall(self._requested.clear)
33 self._clear_task.start(60)
35 self._last_notified_size = 0
37 self.updated = variable.Event()
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)
46 highest_head = max(self._tracker.heads, key=lambda h: self._tracker.get_height_and_last(h)[0]) if self._tracker.heads else None
47 if highest_head is None:
48 return # wait for think2
49 height, last = self._tracker.get_height_and_last(highest_head)
50 if height < self._backlog_needed:
53 log.err(None, 'Error in HeightTracker._think:')
56 self._request(self._best_block_func())
58 def _heard_headers(self, headers):
60 for header in headers:
61 hw = HeaderWrapper.from_header(header)
62 if hw.hash in self._tracker.items:
67 self.updated.happened()
70 if len(self._tracker.items) >= self._last_notified_size + 100:
71 print 'Have %i/%i block headers' % (len(self._tracker.items), self._backlog_needed)
72 self._last_notified_size = len(self._tracker.items)
74 @defer.inlineCallbacks
75 def _request(self, last):
76 if last in self._tracker.items:
78 if last in self._requested:
80 self._requested.add(last)
81 (yield self._factory.getProtocol()).send_getheaders(version=1, have=[], last=last)
83 def get_height_rel_highest(self, block_hash):
84 # callers: highest height can change during yields!
85 best_height, best_last = self._tracker.get_height_and_last(self._best_block_func())
86 height, last = self._tracker.get_height_and_last(block_hash)
88 return -1000000000 # XXX hack
89 return height - best_height
91 @defer.inlineCallbacks
92 def get_height_rel_highest_func(bitcoind, factory, best_block_func, net):
93 if '\ngetblock ' in (yield deferral.retry()(bitcoind.rpc_help)()):
94 @deferral.DeferredCacher
95 @defer.inlineCallbacks
96 def height_cacher(block_hash):
98 x = yield bitcoind.rpc_getblock('%x' % (block_hash,))
99 except jsonrpc.Error_for_code(-5): # Block not found
101 raise deferral.RetrySilentlyException()
104 defer.returnValue(x['blockcount'] if 'blockcount' in x else x['height'])
105 best_height_cached = variable.Variable((yield deferral.retry()(height_cacher)(best_block_func())))
106 def get_height_rel_highest(block_hash):
107 this_height = height_cacher.call_now(block_hash, 0)
108 best_height = height_cacher.call_now(best_block_func(), 0)
109 best_height_cached.set(max(best_height_cached.value, this_height, best_height))
110 return this_height - best_height_cached.value
112 get_height_rel_highest = HeightTracker(best_block_func, factory, 5*net.SHARE_PERIOD*net.CHAIN_LENGTH/net.PARENT.BLOCK_PERIOD).get_height_rel_highest
113 defer.returnValue(get_height_rel_highest)