1
0

async block headers

This commit is contained in:
Janus
2018-08-14 17:50:20 +02:00
committed by SomberNight
parent 19387ff911
commit e9ceeb85af
6 changed files with 364 additions and 37 deletions

View File

@@ -87,7 +87,7 @@ class Interface(PrintError):
return False
return True
@util.aiosafe
@aiosafe
async def run(self):
if self.protocol != 's':
await self.open_session(None, exit_early=False)
@@ -183,11 +183,15 @@ class Interface(PrintError):
self.tip = subscription_res['height']
self.mark_ready()
self.session = session
copy_header_queue = asyncio.Queue()
conniface = Conn(self.server, session, lambda idx, tip: self.network.request_chunk(idx, tip, session))
block_retriever = asyncio.get_event_loop().create_task(self.run_fetch_blocks(subscription_res, copy_header_queue, conniface))
while True:
try:
new_header = await asyncio.wait_for(header_queue.get(), 300)
self.tip_header = new_header
self.tip = new_header['block_height']
await copy_header_queue.put(new_header)
except concurrent.futures.TimeoutError:
await asyncio.wait_for(session.send_request('server.ping'), 5)
@@ -197,6 +201,212 @@ class Interface(PrintError):
def close(self):
self.fut.cancel()
@aiosafe
async def run_fetch_blocks(self, sub_reply, replies, conniface):
async with self.network.bhi_lock:
bhi = BlockHeaderInterface(conniface, sub_reply['height'], self.blockchain.height()+1, self)
await replies.put(blockchain.deserialize_header(bfh(sub_reply['hex']), sub_reply['height']))
self.print_error("checking if catched up {}-1 > {}, tip {}".format(sub_reply['height'], self.blockchain.height(), bhi.tip))
if sub_reply['height']-1 > self.blockchain.height():
last_status = await bhi.sync_until()
assert last_status == 'catchup', last_status
assert self.blockchain.height()+1 == bhi.height, (self.blockchain.height(), bhi.height)
while True:
self.network.notify('updated')
item = await replies.get()
async with self.network.bhi_lock:
if self.blockchain.height() >= bhi.height and self.blockchain.check_header(item):
# another interface amended the blockchain
self.print_error("SKIPPING HEADER", bhi.height)
continue
if bhi.tip < bhi.height:
bhi.height = bhi.tip
await bhi.step(item)
bhi.tip = max(bhi.height, bhi.tip)
class BlockHeaderInterface(PrintError):
def __init__(self, conn, tip, height, iface):
self.tip = tip
self.height = height
self.conn = conn
self.iface = iface
def diagnostic_name(self):
return self.conn.server
async def sync_until(self, next_height=None):
if next_height is None:
next_height = self.tip
last = None
while last is None or self.height < next_height:
if next_height > self.height + 10:
could_connect, num_headers = await self.conn.request_chunk(self.height, next_height)
self.tip = max(self.height + num_headers, self.tip)
if not could_connect:
last = await self.step()
self.tip = max(self.height, self.tip)
continue
self.height = (self.height // 2016 * 2016) + num_headers
if self.height > next_height:
assert False, (self.height, self.tip)
last = 'catchup'
else:
last = await self.step()
self.tip = max(self.height, self.tip)
return last
async def step(self, header=None):
assert self.height != 0
if header is None:
header = await self.conn.get_block_header(self.height, 'catchup')
can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](self)
bad_header = None
if not can_connect:
self.print_error("can't connect", self.height)
#backward
bad = self.height
bad_header = header
self.height -= 1
checkp = False
if self.height <= self.iface.network.max_checkpoint():
self.height = self.iface.network.max_checkpoint() + 1
checkp = True
header = await self.conn.get_block_header(self.height, 'backward')
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header)
can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](self)
if checkp:
assert can_connect or chain, (can_connect, chain)
while not chain and not can_connect:
bad = self.height
bad_header = header
delta = self.tip - self.height
next_height = self.tip - 2 * delta
checkp = False
if next_height <= self.iface.network.max_checkpoint():
next_height = self.iface.network.max_checkpoint() + 1
checkp = True
self.height = next_height
header = await self.conn.get_block_header(self.height, 'backward')
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header)
can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](self)
if checkp:
assert can_connect or chain, (can_connect, chain)
self.print_error("exiting backward mode at", self.height)
if can_connect:
self.print_error("could connect", self.height)
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header)
if type(can_connect) is bool:
# mock
self.height += 1
if self.height > self.tip:
assert False
return 'catchup'
self.iface.blockchain = can_connect
self.height += 1
self.iface.blockchain.save_header(header)
return 'catchup'
if not chain:
raise Exception("not chain") # line 931 in 8e69174374aee87d73cd2f8005fbbe87c93eee9c's network.py
# binary
if type(chain) in [int, bool]:
pass # mock
else:
self.iface.blockchain = chain
good = self.height
self.height = (bad + good) // 2
header = await self.conn.get_block_header(self.height, 'binary')
while True:
self.print_error("binary step")
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header)
if chain:
assert bad != self.height, (bad, self.height)
good = self.height
self.iface.blockchain = self.iface.blockchain if type(chain) in [bool, int] else chain
else:
bad = self.height
assert good != self.height
bad_header = header
if bad != good + 1:
self.height = (bad + good) // 2
header = await self.conn.get_block_header(self.height, 'binary')
continue
mock = bad_header and 'mock' in bad_header and bad_header['mock']['connect'](self)
real = not mock and self.iface.blockchain.can_connect(bad_header, check_height=False)
if not real and not mock:
raise Exception('unexpected bad header during binary' + str(bad_header)) # line 948 in 8e69174374aee87d73cd2f8005fbbe87c93eee9c's network.py
branch = blockchain.blockchains.get(bad)
if branch is not None:
ismocking = False
if type(branch) is dict:
ismocking = True
# FIXME: it does not seem sufficient to check that the branch
# contains the bad_header. what if self.blockchain doesn't?
# the chains shouldn't be joined then. observe the incorrect
# joining on regtest with a server that has a fork of height
# one. the problem is observed only if forking is not during
# electrum runtime
if ismocking and branch['check'](bad_header) or not ismocking and branch.check_header(bad_header):
self.print_error('joining chain', bad)
self.height += 1
return 'join'
else:
if ismocking and branch['parent']['check'](header) or not ismocking and branch.parent().check_header(header):
self.print_error('reorg', bad, self.tip)
self.iface.blockchain = branch.parent() if not ismocking else branch['parent']
self.height = bad
header = await self.conn.get_block_header(self.height, 'binary')
else:
if ismocking:
self.height = bad + 1
self.print_error("TODO replace blockchain")
return 'conflict'
self.print_error('forkpoint conflicts with existing fork', branch.path())
branch.write(b'', 0)
branch.save_header(bad_header)
self.iface.blockchain = branch
self.height = bad + 1
return 'conflict'
else:
bh = self.iface.blockchain.height()
if bh > good:
forkfun = self.iface.blockchain.fork
if 'mock' in bad_header:
chain = bad_header['mock']['check'](bad_header)
forkfun = bad_header['mock']['fork'] if 'fork' in bad_header['mock'] else forkfun
else:
chain = self.iface.blockchain.check_header(bad_header)
if not chain:
b = forkfun(bad_header)
assert bad not in blockchain.blockchains, (bad, list(blockchain.blockchains.keys()))
blockchain.blockchains[bad] = b
self.iface.blockchain = b
self.height = b.forkpoint + 1
assert b.forkpoint == bad
return 'fork'
else:
assert bh == good
if bh < self.tip:
self.print_error("catching up from %d"% (bh + 1))
self.height = bh + 1
return 'no_fork'
class Conn:
def __init__(self, server, session, get_chunk):
self.server = server
self.session = session # type: aiorpcx.ClientSession
self.request_chunk = get_chunk
async def get_block_header(self, height, assert_mode):
res = await asyncio.wait_for(self.session.send_request('blockchain.block.header', [height]), 1)
return blockchain.deserialize_header(bytes.fromhex(res), height)
def check_cert(host, cert):
try:
b = pem.dePem(cert, 'CERTIFICATE')