fixed reorg issue with headers

This commit is contained in:
Lex Berezhny 2019-06-18 17:50:41 -04:00
parent 794eac0519
commit ecebbc86c6
4 changed files with 28 additions and 30 deletions

View file

@ -1,5 +1,4 @@
import logging import logging
from unittest import skip
from torba.testcase import IntegrationTestCase from torba.testcase import IntegrationTestCase
@ -8,30 +7,23 @@ class BlockchainReorganizationTests(IntegrationTestCase):
VERBOSITY = logging.WARN VERBOSITY = logging.WARN
async def test_reorg(self): async def test_reorg(self):
# invalidate current block, move forward 2
self.assertEqual(self.ledger.headers.height, 200) self.assertEqual(self.ledger.headers.height, 200)
self.assertEqual(
await self.blockchain.generate(1) self.ledger.headers.hash(200).decode(),
await self.on_header(201) await self.blockchain.get_block_hash(200)
)
await self.blockchain.invalidate_block(self.ledger.headers.hash(200).decode())
await self.blockchain.generate(2)
await self.ledger.on_header.where(lambda e: e.height == 201)
self.assertEqual(self.ledger.headers.height, 201) self.assertEqual(self.ledger.headers.height, 201)
height = 201 self.assertEqual(
self.ledger.headers.hash(200).decode(),
await self.blockchain.get_block_hash(200)
)
# simple fork (rewind+advance to immediate best) # invalidate current block, move forward 3
height = await self._simulate_reorg(height, 1, 1, 2) await self.blockchain.invalidate_block(self.ledger.headers.hash(200).decode())
height = await self._simulate_reorg(height, 2, 1, 10) await self.blockchain.generate(3)
height = await self._simulate_reorg(height, 4, 1, 3) await self.ledger.on_header.where(lambda e: e.height == 202)
# lagged fork (rewind+batch catch up with immediate best) self.assertEqual(self.ledger.headers.height, 202)
height = await self._simulate_reorg(height, 4, 2, 3)
await self._simulate_reorg(height, 4, 4, 3)
async def _simulate_reorg(self, height, rewind, winners, progress):
for i in range(rewind):
await self.blockchain.invalidateblock(self.ledger.headers.hash(height - i).decode())
await self.blockchain.generate(rewind + winners)
height = height + winners
await self.on_header(height)
for i in range(progress):
await self.blockchain.generate(1)
height += 1
await self.on_header(height)
self.assertEqual(height, self.ledger.headers.height)
return height

View file

@ -39,7 +39,10 @@ class BaseHeaders:
async def open(self): async def open(self):
if self.path != ':memory:': if self.path != ':memory:':
self.io = open(self.path, 'a+b') if not os.path.exists(self.path):
self.io = open(self.path, 'w+b')
else:
self.io = open(self.path, 'r+b')
async def close(self): async def close(self):
self.io.close() self.io.close()
@ -105,7 +108,7 @@ class BaseHeaders:
await loop.run_in_executor(None, self.validate_chunk, height, chunk) await loop.run_in_executor(None, self.validate_chunk, height, chunk)
except InvalidHeader as e: except InvalidHeader as e:
bail = True bail = True
chunk = chunk[:(height-e.height+1)*self.header_size] chunk = chunk[:(height-e.height)*self.header_size]
written = 0 written = 0
if chunk: if chunk:
self.io.seek(height * self.header_size, os.SEEK_SET) self.io.seek(height * self.header_size, os.SEEK_SET)

View file

@ -364,9 +364,12 @@ class BlockchainNode:
self._block_expected += blocks self._block_expected += blocks
return self._cli_cmnd('generate', str(blocks)) return self._cli_cmnd('generate', str(blocks))
def invalidateblock(self, blockhash): def invalidate_block(self, blockhash):
return self._cli_cmnd('invalidateblock', blockhash) return self._cli_cmnd('invalidateblock', blockhash)
def get_block_hash(self, block):
return self._cli_cmnd('getblockhash', str(block))
def get_raw_change_address(self): def get_raw_change_address(self):
return self._cli_cmnd('getrawchangeaddress') return self._cli_cmnd('getrawchangeaddress')

View file

@ -457,13 +457,13 @@ class BlockProcessor:
hash_to_hex_str(self.tip), hash_to_hex_str(self.tip),
self.height)) self.height))
self.tip = coin.header_prevhash(block.header) self.tip = coin.header_prevhash(block.header)
self.backup_txs(block.transactions) self.backup_txs(self.height, block.transactions)
self.height -= 1 self.height -= 1
self.db.tx_counts.pop() self.db.tx_counts.pop()
self.logger.info('backed up to height {:,d}'.format(self.height)) self.logger.info('backed up to height {:,d}'.format(self.height))
def backup_txs(self, txs): def backup_txs(self, height, txs):
# 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.db.read_undo_info(self.height) undo_info = self.db.read_undo_info(self.height)