|
@ -171,6 +171,13 @@ class Interface(PrintError): |
|
|
except ValueError: |
|
|
except ValueError: |
|
|
return None |
|
|
return None |
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
async def request_chunk(self, idx, tip): |
|
|
|
|
|
return await self.network.request_chunk(idx, tip, self.session) |
|
|
|
|
|
|
|
|
async def open_session(self, sslc, exit_early): |
|
|
async def open_session(self, sslc, exit_early): |
|
|
header_queue = asyncio.Queue() |
|
|
header_queue = asyncio.Queue() |
|
|
async with NotificationSession(None, header_queue, self.host, self.port, ssl=sslc, proxy=self.proxy) as session: |
|
|
async with NotificationSession(None, header_queue, self.host, self.port, ssl=sslc, proxy=self.proxy) as session: |
|
@ -184,8 +191,7 @@ class Interface(PrintError): |
|
|
self.mark_ready() |
|
|
self.mark_ready() |
|
|
self.session = session |
|
|
self.session = session |
|
|
copy_header_queue = asyncio.Queue() |
|
|
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)) |
|
|
block_retriever = asyncio.get_event_loop().create_task(self.run_fetch_blocks(subscription_res, copy_header_queue, conniface)) |
|
|
|
|
|
while True: |
|
|
while True: |
|
|
try: |
|
|
try: |
|
|
new_header = await asyncio.wait_for(header_queue.get(), 300) |
|
|
new_header = await asyncio.wait_for(header_queue.get(), 300) |
|
@ -195,121 +201,109 @@ class Interface(PrintError): |
|
|
except concurrent.futures.TimeoutError: |
|
|
except concurrent.futures.TimeoutError: |
|
|
await asyncio.wait_for(session.send_request('server.ping'), 5) |
|
|
await asyncio.wait_for(session.send_request('server.ping'), 5) |
|
|
|
|
|
|
|
|
def queue_request(self, method, params, msg_id): |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def close(self): |
|
|
def close(self): |
|
|
self.fut.cancel() |
|
|
self.fut.cancel() |
|
|
|
|
|
|
|
|
@aiosafe |
|
|
@aiosafe |
|
|
async def run_fetch_blocks(self, sub_reply, replies, conniface): |
|
|
async def run_fetch_blocks(self, sub_reply, replies): |
|
|
async with self.network.bhi_lock: |
|
|
async with self.network.bhi_lock: |
|
|
bhi = BlockHeaderInterface(conniface, self.blockchain.height()+1, self) |
|
|
height = self.blockchain.height()+1 |
|
|
await replies.put(blockchain.deserialize_header(bfh(sub_reply['hex']), sub_reply['height'])) |
|
|
await replies.put(blockchain.deserialize_header(bfh(sub_reply['hex']), sub_reply['height'])) |
|
|
|
|
|
|
|
|
while True: |
|
|
while True: |
|
|
self.network.notify('updated') |
|
|
self.network.notify('updated') |
|
|
item = await replies.get() |
|
|
item = await replies.get() |
|
|
async with self.network.bhi_lock: |
|
|
async with self.network.bhi_lock: |
|
|
if self.blockchain.height()-1 < item['block_height']: |
|
|
if self.blockchain.height() < item['block_height']-1: |
|
|
await bhi.sync_until() |
|
|
_, height = await self.sync_until(height, None) |
|
|
if self.blockchain.height() >= bhi.height and self.blockchain.check_header(item): |
|
|
if self.blockchain.height() >= height and self.blockchain.check_header(item): |
|
|
# another interface amended the blockchain |
|
|
# another interface amended the blockchain |
|
|
self.print_error("SKIPPING HEADER", bhi.height) |
|
|
self.print_error("SKIPPING HEADER", height) |
|
|
continue |
|
|
continue |
|
|
if self.tip < bhi.height: |
|
|
if self.tip < height: |
|
|
bhi.height = self.tip |
|
|
height = self.tip |
|
|
await bhi.step(item) |
|
|
_, height = await self.step(height, item) |
|
|
self.tip = max(bhi.height, self.tip) |
|
|
self.tip = max(height, self.tip) |
|
|
|
|
|
|
|
|
class BlockHeaderInterface(PrintError): |
|
|
|
|
|
def __init__(self, conn, height, iface): |
|
|
|
|
|
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): |
|
|
async def sync_until(self, height, next_height=None): |
|
|
if next_height is None: |
|
|
if next_height is None: |
|
|
next_height = self.iface.tip |
|
|
next_height = self.tip |
|
|
last = None |
|
|
last = None |
|
|
while last is None or self.height < next_height: |
|
|
while last is None or height < next_height: |
|
|
if next_height > self.height + 10: |
|
|
if next_height > height + 10: |
|
|
could_connect, num_headers = await self.conn.request_chunk(self.height, next_height) |
|
|
could_connect, num_headers = await self.request_chunk(height, next_height) |
|
|
self.iface.tip = max(self.height + num_headers, self.iface.tip) |
|
|
self.tip = max(height + num_headers, self.tip) |
|
|
if not could_connect: |
|
|
if not could_connect: |
|
|
if self.height <= self.iface.network.max_checkpoint(): |
|
|
if height <= self.network.max_checkpoint(): |
|
|
raise Exception('server chain conflicts with checkpoints or genesis') |
|
|
raise Exception('server chain conflicts with checkpoints or genesis') |
|
|
last = await self.step() |
|
|
last, height = await self.step(height) |
|
|
self.iface.tip = max(self.height, self.iface.tip) |
|
|
self.tip = max(height, self.tip) |
|
|
continue |
|
|
continue |
|
|
self.height = (self.height // 2016 * 2016) + num_headers |
|
|
height = (height // 2016 * 2016) + num_headers |
|
|
if self.height > next_height: |
|
|
if height > next_height: |
|
|
assert False, (self.height, self.iface.tip) |
|
|
assert False, (height, self.tip) |
|
|
last = 'catchup' |
|
|
last = 'catchup' |
|
|
else: |
|
|
else: |
|
|
last = await self.step() |
|
|
last, height = await self.step(height) |
|
|
self.iface.tip = max(self.height, self.iface.tip) |
|
|
self.tip = max(height, self.tip) |
|
|
return last |
|
|
return last, height |
|
|
|
|
|
|
|
|
async def step(self, header=None): |
|
|
async def step(self, height, header=None): |
|
|
assert self.height != 0 |
|
|
assert height != 0 |
|
|
if header is None: |
|
|
if header is None: |
|
|
header = await self.conn.get_block_header(self.height, 'catchup') |
|
|
header = await self.get_block_header(height, 'catchup') |
|
|
chain = self.iface.blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) |
|
|
chain = self.blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) |
|
|
if chain: return 'catchup' |
|
|
if chain: return 'catchup', height |
|
|
can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](self) |
|
|
can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height) |
|
|
|
|
|
|
|
|
bad_header = None |
|
|
bad_header = None |
|
|
if not can_connect: |
|
|
if not can_connect: |
|
|
self.print_error("can't connect", self.height) |
|
|
self.print_error("can't connect", height) |
|
|
#backward |
|
|
#backward |
|
|
bad = self.height |
|
|
bad = height |
|
|
bad_header = header |
|
|
bad_header = header |
|
|
self.height -= 1 |
|
|
height -= 1 |
|
|
checkp = False |
|
|
checkp = False |
|
|
if self.height <= self.iface.network.max_checkpoint(): |
|
|
if height <= self.network.max_checkpoint(): |
|
|
self.height = self.iface.network.max_checkpoint() + 1 |
|
|
height = self.network.max_checkpoint() + 1 |
|
|
checkp = True |
|
|
checkp = True |
|
|
|
|
|
|
|
|
header = await self.conn.get_block_header(self.height, 'backward') |
|
|
header = await self.get_block_header(height, 'backward') |
|
|
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) |
|
|
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) |
|
|
can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height) |
|
|
if checkp: |
|
|
if checkp: |
|
|
assert can_connect or chain, (can_connect, chain) |
|
|
assert can_connect or chain, (can_connect, chain) |
|
|
while not chain and not can_connect: |
|
|
while not chain and not can_connect: |
|
|
bad = self.height |
|
|
bad = height |
|
|
bad_header = header |
|
|
bad_header = header |
|
|
delta = self.iface.tip - self.height |
|
|
delta = self.tip - height |
|
|
next_height = self.iface.tip - 2 * delta |
|
|
next_height = self.tip - 2 * delta |
|
|
checkp = False |
|
|
checkp = False |
|
|
if next_height <= self.iface.network.max_checkpoint(): |
|
|
if next_height <= self.network.max_checkpoint(): |
|
|
next_height = self.iface.network.max_checkpoint() + 1 |
|
|
next_height = self.network.max_checkpoint() + 1 |
|
|
checkp = True |
|
|
checkp = True |
|
|
self.height = next_height |
|
|
height = next_height |
|
|
|
|
|
|
|
|
header = await self.conn.get_block_header(self.height, 'backward') |
|
|
header = await self.get_block_header(height, 'backward') |
|
|
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) |
|
|
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) |
|
|
can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height) |
|
|
if checkp: |
|
|
if checkp: |
|
|
assert can_connect or chain, (can_connect, chain) |
|
|
assert can_connect or chain, (can_connect, chain) |
|
|
self.print_error("exiting backward mode at", self.height) |
|
|
self.print_error("exiting backward mode at", height) |
|
|
if can_connect: |
|
|
if can_connect: |
|
|
self.print_error("could connect", self.height) |
|
|
self.print_error("could connect", height) |
|
|
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) |
|
|
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) |
|
|
|
|
|
|
|
|
if type(can_connect) is bool: |
|
|
if type(can_connect) is bool: |
|
|
# mock |
|
|
# mock |
|
|
self.height += 1 |
|
|
height += 1 |
|
|
if self.height > self.iface.tip: |
|
|
if height > self.tip: |
|
|
assert False |
|
|
assert False |
|
|
return 'catchup' |
|
|
return 'catchup', height |
|
|
self.iface.blockchain = can_connect |
|
|
self.blockchain = can_connect |
|
|
self.height += 1 |
|
|
height += 1 |
|
|
self.iface.blockchain.save_header(header) |
|
|
self.blockchain.save_header(header) |
|
|
return 'catchup' |
|
|
return 'catchup', height |
|
|
|
|
|
|
|
|
if not chain: |
|
|
if not chain: |
|
|
raise Exception("not chain") # line 931 in 8e69174374aee87d73cd2f8005fbbe87c93eee9c's network.py |
|
|
raise Exception("not chain") # line 931 in 8e69174374aee87d73cd2f8005fbbe87c93eee9c's network.py |
|
@ -318,27 +312,27 @@ class BlockHeaderInterface(PrintError): |
|
|
if type(chain) in [int, bool]: |
|
|
if type(chain) in [int, bool]: |
|
|
pass # mock |
|
|
pass # mock |
|
|
else: |
|
|
else: |
|
|
self.iface.blockchain = chain |
|
|
self.blockchain = chain |
|
|
good = self.height |
|
|
good = height |
|
|
self.height = (bad + good) // 2 |
|
|
height = (bad + good) // 2 |
|
|
header = await self.conn.get_block_header(self.height, 'binary') |
|
|
header = await self.get_block_header(height, 'binary') |
|
|
while True: |
|
|
while True: |
|
|
self.print_error("binary step") |
|
|
self.print_error("binary step") |
|
|
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) |
|
|
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) |
|
|
if chain: |
|
|
if chain: |
|
|
assert bad != self.height, (bad, self.height) |
|
|
assert bad != height, (bad, height) |
|
|
good = self.height |
|
|
good = height |
|
|
self.iface.blockchain = self.iface.blockchain if type(chain) in [bool, int] else chain |
|
|
self.blockchain = self.blockchain if type(chain) in [bool, int] else chain |
|
|
else: |
|
|
else: |
|
|
bad = self.height |
|
|
bad = height |
|
|
assert good != self.height |
|
|
assert good != height |
|
|
bad_header = header |
|
|
bad_header = header |
|
|
if bad != good + 1: |
|
|
if bad != good + 1: |
|
|
self.height = (bad + good) // 2 |
|
|
height = (bad + good) // 2 |
|
|
header = await self.conn.get_block_header(self.height, 'binary') |
|
|
header = await self.get_block_header(height, 'binary') |
|
|
continue |
|
|
continue |
|
|
mock = bad_header and 'mock' in bad_header and bad_header['mock']['connect'](self) |
|
|
mock = bad_header and 'mock' in bad_header and bad_header['mock']['connect'](height) |
|
|
real = not mock and self.iface.blockchain.can_connect(bad_header, check_height=False) |
|
|
real = not mock and self.blockchain.can_connect(bad_header, check_height=False) |
|
|
if not real and not mock: |
|
|
if not real and not mock: |
|
|
raise Exception('unexpected bad header during binary' + str(bad_header)) # line 948 in 8e69174374aee87d73cd2f8005fbbe87c93eee9c's network.py |
|
|
raise Exception('unexpected bad header during binary' + str(bad_header)) # line 948 in 8e69174374aee87d73cd2f8005fbbe87c93eee9c's network.py |
|
|
branch = blockchain.blockchains.get(bad) |
|
|
branch = blockchain.blockchains.get(bad) |
|
@ -354,58 +348,48 @@ class BlockHeaderInterface(PrintError): |
|
|
# electrum runtime |
|
|
# electrum runtime |
|
|
if ismocking and branch['check'](bad_header) or not ismocking and branch.check_header(bad_header): |
|
|
if ismocking and branch['check'](bad_header) or not ismocking and branch.check_header(bad_header): |
|
|
self.print_error('joining chain', bad) |
|
|
self.print_error('joining chain', bad) |
|
|
self.height += 1 |
|
|
height += 1 |
|
|
return 'join' |
|
|
return 'join', height |
|
|
else: |
|
|
else: |
|
|
if ismocking and branch['parent']['check'](header) or not ismocking and branch.parent().check_header(header): |
|
|
if ismocking and branch['parent']['check'](header) or not ismocking and branch.parent().check_header(header): |
|
|
self.print_error('reorg', bad, self.iface.tip) |
|
|
self.print_error('reorg', bad, self.tip) |
|
|
self.iface.blockchain = branch.parent() if not ismocking else branch['parent'] |
|
|
self.blockchain = branch.parent() if not ismocking else branch['parent'] |
|
|
self.height = bad |
|
|
height = bad |
|
|
header = await self.conn.get_block_header(self.height, 'binary') |
|
|
header = await self.get_block_header(height, 'binary') |
|
|
else: |
|
|
else: |
|
|
if ismocking: |
|
|
if ismocking: |
|
|
self.height = bad + 1 |
|
|
height = bad + 1 |
|
|
self.print_error("TODO replace blockchain") |
|
|
self.print_error("TODO replace blockchain") |
|
|
return 'conflict' |
|
|
return 'conflict', height |
|
|
self.print_error('forkpoint conflicts with existing fork', branch.path()) |
|
|
self.print_error('forkpoint conflicts with existing fork', branch.path()) |
|
|
branch.write(b'', 0) |
|
|
branch.write(b'', 0) |
|
|
branch.save_header(bad_header) |
|
|
branch.save_header(bad_header) |
|
|
self.iface.blockchain = branch |
|
|
self.blockchain = branch |
|
|
self.height = bad + 1 |
|
|
height = bad + 1 |
|
|
return 'conflict' |
|
|
return 'conflict', height |
|
|
else: |
|
|
else: |
|
|
bh = self.iface.blockchain.height() |
|
|
bh = self.blockchain.height() |
|
|
if bh > good: |
|
|
if bh > good: |
|
|
forkfun = self.iface.blockchain.fork |
|
|
forkfun = self.blockchain.fork |
|
|
if 'mock' in bad_header: |
|
|
if 'mock' in bad_header: |
|
|
chain = bad_header['mock']['check'](bad_header) |
|
|
chain = bad_header['mock']['check'](bad_header) |
|
|
forkfun = bad_header['mock']['fork'] if 'fork' in bad_header['mock'] else forkfun |
|
|
forkfun = bad_header['mock']['fork'] if 'fork' in bad_header['mock'] else forkfun |
|
|
else: |
|
|
else: |
|
|
chain = self.iface.blockchain.check_header(bad_header) |
|
|
chain = self.blockchain.check_header(bad_header) |
|
|
if not chain: |
|
|
if not chain: |
|
|
b = forkfun(bad_header) |
|
|
b = forkfun(bad_header) |
|
|
assert bad not in blockchain.blockchains, (bad, list(blockchain.blockchains.keys())) |
|
|
assert bad not in blockchain.blockchains, (bad, list(blockchain.blockchains.keys())) |
|
|
blockchain.blockchains[bad] = b |
|
|
blockchain.blockchains[bad] = b |
|
|
self.iface.blockchain = b |
|
|
self.blockchain = b |
|
|
self.height = b.forkpoint + 1 |
|
|
height = b.forkpoint + 1 |
|
|
assert b.forkpoint == bad |
|
|
assert b.forkpoint == bad |
|
|
return 'fork' |
|
|
return 'fork', height |
|
|
else: |
|
|
else: |
|
|
assert bh == good |
|
|
assert bh == good |
|
|
if bh < self.iface.tip: |
|
|
if bh < self.tip: |
|
|
self.print_error("catching up from %d"% (bh + 1)) |
|
|
self.print_error("catching up from %d"% (bh + 1)) |
|
|
self.height = bh + 1 |
|
|
height = bh + 1 |
|
|
return 'no_fork' |
|
|
return 'no_fork', height |
|
|
|
|
|
|
|
|
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): |
|
|
def check_cert(host, cert): |
|
|
try: |
|
|
try: |
|
|