@ -43,8 +43,8 @@ class Prefetcher(LoggedClass):
self . semaphore = asyncio . Semaphore ( )
self . queue = asyncio . Queue ( )
self . queue_size = 0
self . caught_up = False
self . fetched_height = height
self . mempool_hashes = [ ]
# Target cache size. Has little effect on sync time.
self . target_cache_size = 10 * 1024 * 1024
# First fetch to be 10 blocks
@ -64,13 +64,14 @@ class Prefetcher(LoggedClass):
self . fetched_height = height
async def get_blocks ( self ) :
''' Returns a list of prefetched blocks and the mempool. '''
blocks , height , size = await self . queue . get ( )
''' Blocking function that returns prefetched blocks.
The returned result empty just once - when the prefetcher
has caught up with the daemon .
'''
blocks , size = await self . queue . get ( )
self . queue_size - = size
if height == self . daemon . cached_height ( ) :
return blocks , self . mempool_hashes
else :
return blocks , None
return blocks
async def main_loop ( self ) :
''' Loop forever polling for more blocks. '''
@ -78,39 +79,19 @@ class Prefetcher(LoggedClass):
. format ( await self . daemon . height ( ) ) )
while True :
try :
if await self . _caught_up ( ) :
await asyncio . sleep ( 5 )
else :
await asyncio . sleep ( 0 )
with await self . semaphore :
await self . _prefetch ( )
await asyncio . sleep ( 5 if self . caught_up else 0 )
except DaemonError as e :
self . logger . info ( ' ignoring daemon error: {} ' . format ( e ) )
except asyncio . CancelledError :
break
async def _caught_up ( self ) :
''' Poll for new blocks and mempool state.
Mempool is only queried if caught up with daemon . '''
with await self . semaphore :
blocks , size = await self . _prefetch ( )
self . fetched_height + = len ( blocks )
caught_up = self . fetched_height == self . daemon . cached_height ( )
if caught_up :
self . mempool_hashes = await self . daemon . mempool_hashes ( )
# Wake up block processor if we have something
if blocks or caught_up :
self . queue . put_nowait ( ( blocks , self . fetched_height , size ) )
self . queue_size + = size
return caught_up
async def _prefetch ( self ) :
''' Prefetch blocks unless the prefetch queue is full. '''
if self . queue_size > = self . target_cache_size :
return [ ] , 0
return
caught_up = self . daemon . cached_height ( ) == self . fetched_height
daemon_height = await self . daemon . height ( )
cache_room = self . target_cache_size / / self . ave_size
@ -119,15 +100,18 @@ class Prefetcher(LoggedClass):
count = min ( daemon_height - self . fetched_height , cache_room )
count = min ( 4000 , max ( count , 0 ) )
if not count :
return [ ] , 0
# Indicate when we have caught up for the first time only
if not self . caught_up :
self . caught_up = True
self . queue . put_nowait ( ( [ ] , 0 ) )
return
first = self . fetched_height + 1
hex_hashes = await self . daemon . block_hex_hashes ( first , count )
if caught_up :
if self . caught_up :
self . logger . info ( ' new block height {:,d} hash {} '
. format ( first + count - 1 , hex_hashes [ - 1 ] ) )
blocks = await self . daemon . raw_blocks ( hex_hashes )
size = sum ( len ( block ) for block in blocks )
# Update our recent average block size estimate
@ -136,7 +120,9 @@ class Prefetcher(LoggedClass):
else :
self . ave_size = ( size + ( 10 - count ) * self . ave_size ) / / 10
return blocks , size
self . fetched_height + = len ( blocks )
self . queue . put_nowait ( ( blocks , size ) )
self . queue_size + = size
class ChainReorg ( Exception ) :
@ -162,6 +148,7 @@ class BlockProcessor(server.db.DB):
self . daemon = Daemon ( env . daemon_url , env . debug )
self . daemon . debug_set_height ( self . height )
self . caught_up = False
self . touched = set ( )
self . futures = [ ]
@ -223,41 +210,51 @@ class BlockProcessor(server.db.DB):
await asyncio . sleep ( 0 )
async def _wait_for_update ( self ) :
''' Wait for the prefetcher to deliver blocks or a mempool update .
''' Wait for the prefetcher to deliver blocks.
Blocks are only processed in the forward direction . The
prefetcher only provides a non - None mempool when caught up .
Blocks are only processed in the forward direction .
'''
blocks , mempool_hashes = await self . prefetcher . get_blocks ( )
blocks = await self . prefetcher . get_blocks ( )
if not blocks :
await self . first_caught_up ( )
return
''' Strip the unspendable genesis coinbase. '''
if self . height == - 1 :
blocks [ 0 ] = blocks [ 0 ] [ : self . coin . HEADER_LEN ] + bytes ( 1 )
caught_up = mempool_hashes is not None
try :
for block in blocks :
self . advance_block ( block , caught_up )
if not caught_up and time . time ( ) > self . next_cache_check :
self . check_cache_size ( )
self . next_cache_check = time . time ( ) + 60
self . advance_block ( block , self . caught_up )
await asyncio . sleep ( 0 ) # Yield
if caught_up :
await self . caught_up ( mempool_hashes )
self . touched = set ( )
except ChainReorg :
await self . handle_chain_reorg ( )
async def caught_up ( self , mempool_hashes ) :
if self . caught_up :
# Flush everything as queries are performed on the DB and
# not in-memory.
self . flush ( True )
self . notify ( self . touched )
elif time . time ( ) > self . next_cache_check :
self . check_cache_size ( )
self . next_cache_check = time . time ( ) + 60
self . touched = set ( )
async def first_caught_up ( self ) :
''' Called after each deamon poll if caught up. '''
# Caught up to daemon height. Flush everything as queries
# are performed on the DB and not in-memory.
self . caught_up = True
if self . first_sync :
self . first_sync = False
self . logger . info ( ' {} synced to height {:,d} . DB version: '
. format ( VERSION , self . height , self . db_version ) )
self . flush ( True )
def notify ( self , touched ) :
''' Called with list of touched addresses by new blocks.
Only called for blocks found after first_caught_up is called .
Intended to be overridden in derived classes . '''
async def handle_chain_reorg ( self ) :
# First get all state on disk
self . logger . info ( ' chain reorg detected ' )