Locate blockchain connection point with binary search
This commit is contained in:
parent
353a7b8fd9
commit
6321b14f9f
|
@ -247,35 +247,21 @@ class Blockchain(util.PrintError):
|
||||||
new_bits = bitsN << 24 | bitsBase
|
new_bits = bitsN << 24 | bitsBase
|
||||||
return new_bits, bitsBase << (8 * (bitsN-3))
|
return new_bits, bitsBase << (8 * (bitsN-3))
|
||||||
|
|
||||||
def connect_header(self, chain, header):
|
def can_connect(self, header):
|
||||||
'''Builds a header chain until it connects. Returns True if it has
|
|
||||||
successfully connected, False if verification failed, otherwise the
|
|
||||||
height of the next header needed.'''
|
|
||||||
chain.append(header) # Ordered by decreasing height
|
|
||||||
previous_height = header['block_height'] - 1
|
previous_height = header['block_height'] - 1
|
||||||
previous_header = self.read_header(previous_height)
|
previous_header = self.read_header(previous_height)
|
||||||
|
|
||||||
# Missing header, request it
|
|
||||||
if not previous_header:
|
if not previous_header:
|
||||||
return previous_height
|
return False
|
||||||
|
|
||||||
# Does it connect to my chain?
|
|
||||||
prev_hash = self.hash_header(previous_header)
|
prev_hash = self.hash_header(previous_header)
|
||||||
if prev_hash != header.get('prev_block_hash'):
|
if prev_hash != header.get('prev_block_hash'):
|
||||||
self.print_error("reorg")
|
|
||||||
return previous_height
|
|
||||||
|
|
||||||
# The chain is complete. Reverse to order by increasing height
|
|
||||||
chain.reverse()
|
|
||||||
try:
|
|
||||||
self.verify_chain(chain)
|
|
||||||
self.print_error("new height:", previous_height + len(chain))
|
|
||||||
for header in chain:
|
|
||||||
self.save_header(header)
|
|
||||||
return True
|
|
||||||
except BaseException as e:
|
|
||||||
self.print_error(str(e))
|
|
||||||
return False
|
return False
|
||||||
|
height = header.get('block_height')
|
||||||
|
bits, target = self.get_target(height / 2016)
|
||||||
|
try:
|
||||||
|
self.verify_header(header, previous_header, bits, target)
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def connect_chunk(self, idx, hexdata):
|
def connect_chunk(self, idx, hexdata):
|
||||||
try:
|
try:
|
||||||
|
@ -283,10 +269,10 @@ class Blockchain(util.PrintError):
|
||||||
self.verify_chunk(idx, data)
|
self.verify_chunk(idx, data)
|
||||||
self.print_error("validated chunk %d" % idx)
|
self.print_error("validated chunk %d" % idx)
|
||||||
self.save_chunk(idx, data)
|
self.save_chunk(idx, data)
|
||||||
return idx + 1
|
return True
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
self.print_error('verify_chunk failed', str(e))
|
self.print_error('verify_chunk failed', str(e))
|
||||||
return idx - 1
|
return False
|
||||||
|
|
||||||
def get_checkpoint(self):
|
def get_checkpoint(self):
|
||||||
height = self.config.get('checkpoint_height', 0)
|
height = self.config.get('checkpoint_height', 0)
|
||||||
|
|
188
lib/network.py
188
lib/network.py
|
@ -554,7 +554,7 @@ class Network(util.DaemonThread):
|
||||||
interface.server_version = result
|
interface.server_version = result
|
||||||
elif method == 'blockchain.headers.subscribe':
|
elif method == 'blockchain.headers.subscribe':
|
||||||
if error is None:
|
if error is None:
|
||||||
self.on_header(interface, result)
|
self.on_notify_header(interface, result)
|
||||||
elif method == 'server.peers.subscribe':
|
elif method == 'server.peers.subscribe':
|
||||||
if error is None:
|
if error is None:
|
||||||
self.irc_servers = parse_servers(result)
|
self.irc_servers = parse_servers(result)
|
||||||
|
@ -691,12 +691,9 @@ class Network(util.DaemonThread):
|
||||||
def new_interface(self, server, socket):
|
def new_interface(self, server, socket):
|
||||||
self.add_recent_server(server)
|
self.add_recent_server(server)
|
||||||
interface = Interface(server, socket)
|
interface = Interface(server, socket)
|
||||||
# A deque of interface header requests, processed left-to-right
|
interface.mode = 'checkpoint'
|
||||||
interface.bc_requests = deque()
|
|
||||||
interface.failed_checkpoint = False
|
|
||||||
self.interfaces[server] = interface
|
self.interfaces[server] = interface
|
||||||
self.queue_request('blockchain.block.get_header', [self.blockchain.checkpoint_height], interface)
|
self.request_header(interface, self.blockchain.checkpoint_height)
|
||||||
self.queue_request('blockchain.headers.subscribe', [], interface)
|
|
||||||
if server == self.default_server:
|
if server == self.default_server:
|
||||||
self.switch_to_interface(server)
|
self.switch_to_interface(server)
|
||||||
self.notify('interfaces')
|
self.notify('interfaces')
|
||||||
|
@ -743,111 +740,124 @@ class Network(util.DaemonThread):
|
||||||
else:
|
else:
|
||||||
self.switch_to_interface(self.default_server)
|
self.switch_to_interface(self.default_server)
|
||||||
|
|
||||||
def request_chunk(self, interface, data, idx):
|
def request_chunk(self, interface, idx):
|
||||||
interface.print_error("requesting chunk %d" % idx)
|
interface.print_error("requesting chunk %d" % idx)
|
||||||
self.queue_request('blockchain.block.get_chunk', [idx], interface)
|
self.queue_request('blockchain.block.get_chunk', [idx], interface)
|
||||||
data['chunk_idx'] = idx
|
interface.request = idx
|
||||||
data['req_time'] = time.time()
|
interface.req_time = time.time()
|
||||||
|
|
||||||
def on_get_chunk(self, interface, response):
|
def on_get_chunk(self, interface, response):
|
||||||
'''Handle receiving a chunk of block headers'''
|
'''Handle receiving a chunk of block headers'''
|
||||||
if response.get('error'):
|
if response.get('error'):
|
||||||
interface.print_error(response.get('error'))
|
interface.print_error(response.get('error'))
|
||||||
return
|
return
|
||||||
if interface.bc_requests:
|
|
||||||
data = interface.bc_requests[0]
|
|
||||||
req_idx = data.get('chunk_idx')
|
|
||||||
# Ignore unsolicited chunks
|
# Ignore unsolicited chunks
|
||||||
if req_idx == response['params'][0]:
|
index = response['params'][0]
|
||||||
idx = self.blockchain.connect_chunk(req_idx, response['result'])
|
if interface.request != index:
|
||||||
|
return
|
||||||
|
connect = self.blockchain.connect_chunk(index, response['result'])
|
||||||
# If not finished, get the next chunk
|
# If not finished, get the next chunk
|
||||||
if idx < 0 or self.get_local_height() >= data['if_height']:
|
if not connect:
|
||||||
interface.bc_requests.popleft()
|
return
|
||||||
self.notify('updated')
|
if self.get_local_height() < interface.tip:
|
||||||
|
self.request_chunk(interface, index+1)
|
||||||
else:
|
else:
|
||||||
self.request_chunk(interface, data, idx)
|
interface.request = None
|
||||||
self.notify('updated')
|
self.notify('updated')
|
||||||
|
|
||||||
def request_header(self, interface, data, height):
|
def request_header(self, interface, height):
|
||||||
interface.print_error("requesting header %d" % height)
|
interface.print_error("requesting header %d" % height)
|
||||||
self.queue_request('blockchain.block.get_header', [height], interface)
|
self.queue_request('blockchain.block.get_header', [height], interface)
|
||||||
data['header_height'] = height
|
interface.request = height
|
||||||
data['req_time'] = time.time()
|
interface.req_time = time.time()
|
||||||
if not 'chain' in data:
|
|
||||||
data['chain'] = []
|
|
||||||
|
|
||||||
def on_get_header(self, interface, response):
|
def on_get_header(self, interface, response):
|
||||||
'''Handle receiving a single block header'''
|
'''Handle receiving a single block header'''
|
||||||
# close connection if header does not pass checkpoint
|
|
||||||
if not self.blockchain.pass_checkpoint(response['result']):
|
|
||||||
if interface == self.interface and not self.auto_connect:
|
|
||||||
interface.failed_checkpoint = True
|
|
||||||
else:
|
|
||||||
interface.print_error("header did not pass checkpoint, dismissing interface")
|
|
||||||
self.connection_down(interface.server)
|
|
||||||
return
|
|
||||||
if self.blockchain.downloading_headers:
|
if self.blockchain.downloading_headers:
|
||||||
return
|
return
|
||||||
if interface.bc_requests:
|
header = response.get('result')
|
||||||
data = interface.bc_requests[0]
|
if not header:
|
||||||
req_height = data.get('header_height', -1)
|
interface.print_error(response)
|
||||||
# Ignore unsolicited headers
|
self.connection_down(interface.server)
|
||||||
if req_height == response['params'][0]:
|
|
||||||
if interface.failed_checkpoint:
|
|
||||||
interface.bc_requests.popleft()
|
|
||||||
return
|
return
|
||||||
next_height = self.blockchain.connect_header(data['chain'], response['result'])
|
height = header.get('block_height')
|
||||||
# If not finished, get the next header
|
if interface.request != height:
|
||||||
if next_height in [True, False]:
|
interface.print_error("unsolicited header",interface.request, height)
|
||||||
interface.bc_requests.popleft()
|
self.connection_down(interface.server)
|
||||||
if next_height:
|
return
|
||||||
self.switch_lagging_interface(interface.server)
|
self.on_header(interface, header)
|
||||||
self.notify('updated')
|
|
||||||
|
def on_header(self, interface, header):
|
||||||
|
height = header.get('block_height')
|
||||||
|
if interface.mode == 'checkpoint':
|
||||||
|
if self.blockchain.pass_checkpoint(header):
|
||||||
|
interface.mode = 'default'
|
||||||
|
self.queue_request('blockchain.headers.subscribe', [], interface)
|
||||||
else:
|
else:
|
||||||
|
if interface != self.interface or self.auto_connect:
|
||||||
|
interface.print_error("checkpoint failed")
|
||||||
|
self.connection_down(interface.server)
|
||||||
|
interface.request = None
|
||||||
|
return
|
||||||
|
can_connect = self.blockchain.can_connect(header)
|
||||||
|
if interface.mode == 'backward':
|
||||||
|
if can_connect:
|
||||||
|
interface.good = height
|
||||||
|
interface.mode = 'binary'
|
||||||
|
interface.print_error("binary search")
|
||||||
|
next_height = (interface.bad + interface.good) // 2
|
||||||
|
else:
|
||||||
|
interface.bad = height
|
||||||
|
delta = interface.tip - height
|
||||||
|
next_height = interface.tip - 2 * delta
|
||||||
|
if next_height < 0:
|
||||||
interface.print_error("header didn't connect, dismissing interface")
|
interface.print_error("header didn't connect, dismissing interface")
|
||||||
self.connection_down(interface.server)
|
self.connection_down(interface.server)
|
||||||
|
elif interface.mode == 'binary':
|
||||||
|
if can_connect:
|
||||||
|
interface.good = height
|
||||||
else:
|
else:
|
||||||
self.request_header(interface, data, next_height)
|
interface.bad = height
|
||||||
|
if interface.good == interface.bad - 1:
|
||||||
def bc_request_headers(self, interface, data):
|
interface.print_error("catching up from %d"% interface.good)
|
||||||
'''Send a request for the next header, or a chunk of them,
|
interface.mode = 'default'
|
||||||
if necessary.
|
next_height = interface.good
|
||||||
'''
|
|
||||||
if self.blockchain.downloading_headers:
|
|
||||||
return False
|
|
||||||
local_height, if_height = self.get_local_height(), data['if_height']
|
|
||||||
if if_height <= local_height:
|
|
||||||
return False
|
|
||||||
elif if_height > local_height + 50:
|
|
||||||
self.request_chunk(interface, data, (local_height + 1) / 2016)
|
|
||||||
else:
|
else:
|
||||||
self.request_header(interface, data, if_height)
|
next_height = (interface.bad + interface.good) // 2
|
||||||
return True
|
elif interface.mode == 'default':
|
||||||
|
if can_connect:
|
||||||
|
if height > self.get_local_height():
|
||||||
|
self.blockchain.save_header(header)
|
||||||
|
self.notify('updated')
|
||||||
|
if height < interface.tip:
|
||||||
|
next_height = height + 1
|
||||||
|
else:
|
||||||
|
next_height = None
|
||||||
|
else:
|
||||||
|
interface.mode = 'backward'
|
||||||
|
interface.bad = height
|
||||||
|
next_height = height - 1
|
||||||
|
else:
|
||||||
|
raise BaseException(interface.mode)
|
||||||
|
# If not finished, get the next header
|
||||||
|
if next_height:
|
||||||
|
if interface.mode != 'default':
|
||||||
|
self.request_header(interface, next_height)
|
||||||
|
else:
|
||||||
|
local_height = self.get_local_height()
|
||||||
|
if interface.tip > local_height + 50:
|
||||||
|
self.request_chunk(interface, (local_height + 1) // 2016)
|
||||||
|
else:
|
||||||
|
self.request_header(interface, next_height)
|
||||||
|
else:
|
||||||
|
interface.request = None
|
||||||
|
|
||||||
def handle_bc_requests(self):
|
def maintain_requests(self):
|
||||||
'''Work through each interface that has notified us of a new header.
|
|
||||||
Send it requests if it is ahead of our blockchain object.
|
|
||||||
'''
|
|
||||||
for interface in self.interfaces.values():
|
for interface in self.interfaces.values():
|
||||||
if not interface.bc_requests:
|
if interface.request and time.time() - interface.request_time > 20:
|
||||||
continue
|
|
||||||
data = interface.bc_requests.popleft()
|
|
||||||
# If the connection was lost move on
|
|
||||||
if not interface in self.interfaces.values():
|
|
||||||
continue
|
|
||||||
req_time = data.get('req_time')
|
|
||||||
if not req_time:
|
|
||||||
# No requests sent yet. This interface has a new height.
|
|
||||||
# Request headers if it is ahead of our blockchain
|
|
||||||
if not self.bc_request_headers(interface, data):
|
|
||||||
continue
|
|
||||||
elif time.time() - req_time > 20:
|
|
||||||
interface.print_error("blockchain request timed out")
|
interface.print_error("blockchain request timed out")
|
||||||
self.connection_down(interface.server)
|
self.connection_down(interface.server)
|
||||||
continue
|
continue
|
||||||
# Put updated request state back at head of deque
|
|
||||||
interface.bc_requests.appendleft(data)
|
|
||||||
break
|
|
||||||
|
|
||||||
def wait_on_sockets(self):
|
def wait_on_sockets(self):
|
||||||
# Python docs say Windows doesn't like empty selects.
|
# Python docs say Windows doesn't like empty selects.
|
||||||
|
@ -874,21 +884,29 @@ class Network(util.DaemonThread):
|
||||||
while self.is_running():
|
while self.is_running():
|
||||||
self.maintain_sockets()
|
self.maintain_sockets()
|
||||||
self.wait_on_sockets()
|
self.wait_on_sockets()
|
||||||
self.handle_bc_requests()
|
self.maintain_requests()
|
||||||
self.run_jobs() # Synchronizer and Verifier
|
self.run_jobs() # Synchronizer and Verifier
|
||||||
self.process_pending_sends()
|
self.process_pending_sends()
|
||||||
|
|
||||||
self.stop_network()
|
self.stop_network()
|
||||||
self.on_stop()
|
self.on_stop()
|
||||||
|
|
||||||
def on_header(self, i, header):
|
def on_notify_header(self, i, header):
|
||||||
height = header.get('block_height')
|
height = header.get('block_height')
|
||||||
if not height:
|
if not height:
|
||||||
return
|
return
|
||||||
self.headers[i.server] = header
|
self.headers[i.server] = header
|
||||||
|
i.tip = height
|
||||||
|
local_height = self.get_local_height()
|
||||||
|
|
||||||
# Queue this interface's height for asynchronous catch-up
|
if i.tip > local_height:
|
||||||
i.bc_requests.append({'if_height': height})
|
i.print_error("better height", height)
|
||||||
|
# if I can connect, do it right away
|
||||||
|
if self.blockchain.can_connect(header):
|
||||||
|
self.blockchain.save_header(header)
|
||||||
|
self.notify('updated')
|
||||||
|
# otherwise trigger a search
|
||||||
|
elif i.request is None:
|
||||||
|
self.on_header(i, header)
|
||||||
|
|
||||||
if i == self.interface:
|
if i == self.interface:
|
||||||
self.switch_lagging_interface()
|
self.switch_lagging_interface()
|
||||||
|
|
Loading…
Reference in New Issue