Browse Source

Add feature to simulate reorgs for debugging

master
Neil Booth 8 years ago
parent
commit
640360c809
  1. 6
      docs/ENV-NOTES
  2. 34
      server/block_processor.py
  3. 2
      server/env.py

6
docs/ENV-NOTES

@ -82,3 +82,9 @@ UTXO_MB - amount of UTXO and history cache, in MB, to retain before
leveldb caching and Python GC effects. However this may be leveldb caching and Python GC effects. However this may be
very dependent on hardware and you may have different very dependent on hardware and you may have different
results. results.
The following are for debugging purposes.
FORCE_REORG - if set to a positive integer, it will simulate a reorg
of the blockchain for that number of blocks. Do not set
to a value greater than REORG_LIMIT.

34
server/block_processor.py

@ -58,6 +58,7 @@ class Prefetcher(LoggedClass):
self.queue.get_nowait() self.queue.get_nowait()
self.queue_size = 0 self.queue_size = 0
self.fetched_height = height self.fetched_height = height
self.caught_up = False
async def get_blocks(self): async def get_blocks(self):
'''Blocking function that returns prefetched blocks. '''Blocking function that returns prefetched blocks.
@ -185,6 +186,13 @@ class BlockProcessor(server.db.DB):
Safely flushes the DB on clean shutdown. Safely flushes the DB on clean shutdown.
''' '''
self.futures.append(asyncio.ensure_future(self.prefetcher.main_loop())) self.futures.append(asyncio.ensure_future(self.prefetcher.main_loop()))
# Simulate a reorg if requested
if self.env.force_reorg > 0:
self.logger.info('DEBUG: simulating chain reorg of {:,d} blocks'
.format(self.env.force_reorg))
await self.handle_chain_reorg(self.env.force_reorg)
try: try:
while True: while True:
await self._wait_for_update() await self._wait_for_update()
@ -223,7 +231,7 @@ class BlockProcessor(server.db.DB):
self.advance_block(block, self.caught_up) self.advance_block(block, self.caught_up)
await asyncio.sleep(0) # Yield await asyncio.sleep(0) # Yield
except ChainReorg: except ChainReorg:
await self.handle_chain_reorg() await self.handle_chain_reorg(None)
if self.caught_up: if self.caught_up:
# Flush everything as queries are performed on the DB and # Flush everything as queries are performed on the DB and
@ -250,24 +258,26 @@ class BlockProcessor(server.db.DB):
Only called for blocks found after first_caught_up is called. Only called for blocks found after first_caught_up is called.
Intended to be overridden in derived classes.''' Intended to be overridden in derived classes.'''
async def handle_chain_reorg(self): async def handle_chain_reorg(self, count):
# First get all state on disk '''Handle a chain reorganisation.
Count is the number of blocks to simulate a reorg, or None for
a real reorg.'''
self.logger.info('chain reorg detected') self.logger.info('chain reorg detected')
self.flush(True) self.flush(True)
self.logger.info('finding common height...') self.logger.info('finding common height...')
hashes = await self.reorg_hashes() hashes = await self.reorg_hashes(count)
# Reverse and convert to hex strings. # Reverse and convert to hex strings.
hashes = [hash_to_str(hash) for hash in reversed(hashes)] hashes = [hash_to_str(hash) for hash in reversed(hashes)]
for hex_hashes in chunks(hashes, 50): for hex_hashes in chunks(hashes, 50):
blocks = await self.daemon.raw_blocks(hex_hashes) blocks = await self.daemon.raw_blocks(hex_hashes)
self.backup_blocks(blocks) self.backup_blocks(blocks)
self.logger.info('backed up to height {:,d}'.format(self.height))
await self.prefetcher.clear(self.height) await self.prefetcher.clear(self.height)
self.logger.info('prefetcher reset') self.logger.info('prefetcher reset')
async def reorg_hashes(self): async def reorg_hashes(self, count):
'''Return the list of hashes to back up beacuse of a reorg. '''Return the list of hashes to back up beacuse of a reorg.
The hashes are returned in order of increasing height.''' The hashes are returned in order of increasing height.'''
@ -278,6 +288,8 @@ class BlockProcessor(server.db.DB):
return n return n
return -1 return -1
if count is None:
# A real reorg
start = self.height - 1 start = self.height - 1
count = 1 count = 1
while start > 0: while start > 0:
@ -291,11 +303,12 @@ class BlockProcessor(server.db.DB):
count = min(count * 2, start) count = min(count * 2, start)
start -= count start -= count
# Hashes differ from height 'start'
count = (self.height - start) + 1 count = (self.height - start) + 1
else:
start = (self.height - count) + 1
self.logger.info('chain was reorganised for {:,d} blocks from ' self.logger.info('chain was reorganised for {:,d} blocks over '
'height {:,d} to height {:,d}' 'heights {:,d}-{:,d} inclusive'
.format(count, start, start + count - 1)) .format(count, start, start + count - 1))
return self.fs_block_hashes(start, count) return self.fs_block_hashes(start, count)
@ -660,6 +673,9 @@ class BlockProcessor(server.db.DB):
# Prevout values, in order down the block (coinbase first if present) # Prevout values, in order down the block (coinbase first if present)
# undo_info is in reverse block order # undo_info is in reverse block order
undo_info = self.read_undo_info(self.height) undo_info = self.read_undo_info(self.height)
if not undo_info:
raise ChainError('no undo information found for height {:,d}'
.format(self.height))
n = len(undo_info) n = len(undo_info)
# Use local vars for speed in the loops # Use local vars for speed in the loops

2
server/env.py

@ -53,6 +53,8 @@ class Env(LoggedClass):
self.report_host = self.default('REPORT_HOST', self.host) self.report_host = self.default('REPORT_HOST', self.host)
self.irc_nick = self.default('IRC_NICK', None) self.irc_nick = self.default('IRC_NICK', None)
self.irc = self.default('IRC', False) self.irc = self.default('IRC', False)
# Debugging
self.force_reorg = self.integer('FORCE_REORG', 0)
def default(self, envvar, default): def default(self, envvar, default):
return environ.get(envvar, default) return environ.get(envvar, default)

Loading…
Cancel
Save