From 39bc19b643e2f6977074fdb4e92ef72a79106eda Mon Sep 17 00:00:00 2001 From: Mariano Sorgente Date: Fri, 30 Oct 2020 15:56:05 +0900 Subject: [PATCH] Support for reorging genesis block --- src/consensus/find_fork_point.py | 16 ++++++--- src/full_node/blockchain.py | 42 +++++++++++++--------- src/full_node/coin_store.py | 61 ++++++++++---------------------- 3 files changed, 55 insertions(+), 64 deletions(-) diff --git a/src/consensus/find_fork_point.py b/src/consensus/find_fork_point.py index 0cc5c3930a..97a5443979 100644 --- a/src/consensus/find_fork_point.py +++ b/src/consensus/find_fork_point.py @@ -1,10 +1,12 @@ -from typing import Dict, Any +from typing import Dict, Any, Optional from src.util.ints import uint32 -def find_fork_point_in_chain(hash_to_block: Dict, block_1: Any, block_2: Any) -> uint32: +def find_fork_point_in_chain(hash_to_block: Dict, block_1: Any, block_2: Any) -> int: """Tries to find height where new chain (block_2) diverged from block_1 (assuming prev blocks - are all included in chain)""" + are all included in chain) + Returns -1 if chains have no common ancestor + """ while block_2.height > 0 or block_1.height > 0: if block_2.height > block_1.height: block_2 = hash_to_block[block_2.prev_header_hash] @@ -15,5 +17,9 @@ def find_fork_point_in_chain(hash_to_block: Dict, block_1: Any, block_2: Any) -> return block_2.height block_2 = hash_to_block[block_2.prev_header_hash] block_1 = hash_to_block[block_1.prev_header_hash] - assert block_2 == block_1 # Genesis block is the same, genesis fork - return uint32(0) + if block_2 != block_1: + # All blocks are different + return -1 + + # First block is the same + return 0 diff --git a/src/full_node/blockchain.py b/src/full_node/blockchain.py index 3cf2a4c306..aef6656a22 100644 --- a/src/full_node/blockchain.py +++ b/src/full_node/blockchain.py @@ -265,22 +265,25 @@ class Blockchain: block.finished_slots is not None, ) overflow = is_overflow_sub_block(self.constants, ips, required_iters) - prev_sb = self.sub_blocks[block.prev_header_hash] - if prev_sb.deficit == self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: - # Prev sb must be an overflow sb - if overflow and block.finished_slots is None: - # Still overflowed, so we cannot decrease the deficit - deficit: uint8 = prev_sb.deficit - else: - # We have passed the first overflow, can decrease - deficit: uint8 = prev_sb.deficit - 1 - elif prev_sb.deficit == 0: - if block.finished_slots is not None: - deficit = uint8(self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK) - else: - deficit = uint8(0) + if block.height == 0: + deficit = uint8(self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK) - 1 else: - deficit = prev_sb.deficit - 1 + prev_sb = self.sub_blocks[block.prev_header_hash] + if prev_sb.deficit == self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK: + # Prev sb must be an overflow sb + if overflow and block.finished_slots is None: + # Still overflowed, so we cannot decrease the deficit + deficit: uint8 = prev_sb.deficit + else: + # We have passed the first overflow, can decrease + deficit: uint8 = prev_sb.deficit - 1 + elif prev_sb.deficit == 0: + if block.finished_slots is not None: + deficit = uint8(self.constants.MIN_SUB_BLOCKS_PER_CHALLENGE_BLOCK) + else: + deficit = uint8(0) + else: + deficit = prev_sb.deficit - 1 sub_block = full_block_to_sub_block_record(block, ips, required_iters, deficit) @@ -309,19 +312,24 @@ class Blockchain: assert self.get_peak() is not None if sub_block.weight > self.get_peak().weight: # Find the fork. if the block is just being appended, it will return the peak - fork_h: bytes32 = find_fork_point_in_chain(self.sub_blocks, sub_block, self.get_peak()) + # If no blocks in common, returns -1, and reverts all blocks + fork_h: int = find_fork_point_in_chain(self.sub_blocks, sub_block, self.get_peak()) + # Rollback to fork await self.coin_store.rollback_to_block(fork_h) # Collect all blocks from fork point to new peak blocks_to_add: List[Tuple[FullBlock, SubBlockRecord]] = [] curr = sub_block.header_hash - while curr != self.height_to_hash[fork_h]: + while fork_h < 0 or curr != self.height_to_hash[uint32(fork_h)]: fetched_block: Optional[FullBlock] = await self.block_store.get_block(curr) fetched_sub_block: Optional[SubBlockRecord] = await self.block_store.get_sub_block(curr) assert fetched_block is not None assert fetched_sub_block is not None blocks_to_add.append((fetched_block, fetched_sub_block)) + if fetched_block.height == 0: + # Doing a full reorg, starting at height 0 + break curr = fetched_sub_block.prev_hash for fetched_block, fetched_sub_block in reversed(blocks_to_add): diff --git a/src/full_node/coin_store.py b/src/full_node/coin_store.py index 985f3b9d78..cf76d39ce4 100644 --- a/src/full_node/coin_store.py +++ b/src/full_node/coin_store.py @@ -18,9 +18,7 @@ class CoinStore: cache_size: uint32 @classmethod - async def create( - cls, connection: aiosqlite.Connection, cache_size: uint32 = uint32(600000) - ): + async def create(cls, connection: aiosqlite.Connection, cache_size: uint32 = uint32(600000)): self = cls() self.cache_size = cache_size @@ -44,17 +42,11 @@ class CoinStore: "CREATE INDEX IF NOT EXISTS coin_confirmed_index on coin_record(confirmed_index)" ) - await self.coin_record_db.execute( - "CREATE INDEX IF NOT EXISTS coin_spent_index on coin_record(spent_index)" - ) + await self.coin_record_db.execute("CREATE INDEX IF NOT EXISTS coin_spent_index on coin_record(spent_index)") - await self.coin_record_db.execute( - "CREATE INDEX IF NOT EXISTS coin_spent on coin_record(spent)" - ) + await self.coin_record_db.execute("CREATE INDEX IF NOT EXISTS coin_spent on coin_record(spent)") - await self.coin_record_db.execute( - "CREATE INDEX IF NOT EXISTS coin_spent on coin_record(puzzle_hash)" - ) + await self.coin_record_db.execute("CREATE INDEX IF NOT EXISTS coin_spent on coin_record(puzzle_hash)") await self.coin_record_db.commit() self.coin_record_cache = dict() @@ -74,36 +66,26 @@ class CoinStore: coinbase_coin = block.get_coinbase() fees_coin = block.get_fees_coin() - coinbase_r: CoinRecord = CoinRecord( - coinbase_coin, block.height, uint32(0), False, True - ) + coinbase_r: CoinRecord = CoinRecord(coinbase_coin, block.height, uint32(0), False, True) fees_r: CoinRecord = CoinRecord(fees_coin, block.height, uint32(0), False, True) await self._add_coin_record(coinbase_r) await self._add_coin_record(fees_r) # Checks DB and DiffStores for CoinRecord with coin_name and returns it - async def get_coin_record( - self, coin_name: bytes32 - ) -> Optional[CoinRecord]: + async def get_coin_record(self, coin_name: bytes32) -> Optional[CoinRecord]: if coin_name.hex() in self.coin_record_cache: return self.coin_record_cache[coin_name.hex()] - cursor = await self.coin_record_db.execute( - "SELECT * from coin_record WHERE coin_name=?", (coin_name.hex(),) - ) + cursor = await self.coin_record_db.execute("SELECT * from coin_record WHERE coin_name=?", (coin_name.hex(),)) row = await cursor.fetchone() await cursor.close() if row is not None: - coin = Coin( - bytes32(bytes.fromhex(row[6])), bytes32(bytes.fromhex(row[5])), row[7] - ) + coin = Coin(bytes32(bytes.fromhex(row[6])), bytes32(bytes.fromhex(row[5])), row[7]) return CoinRecord(coin, row[1], row[2], row[3], row[4]) return None # Checks DB and DiffStores for CoinRecords with puzzle_hash and returns them - async def get_coin_records_by_puzzle_hash( - self, puzzle_hash: bytes32 - ) -> List[CoinRecord]: + async def get_coin_records_by_puzzle_hash(self, puzzle_hash: bytes32) -> List[CoinRecord]: coins = set() cursor = await self.coin_record_db.execute( "SELECT * from coin_record WHERE puzzle_hash=?", (puzzle_hash.hex(),) @@ -111,17 +93,18 @@ class CoinStore: rows = await cursor.fetchall() await cursor.close() for row in rows: - coin = Coin( - bytes32(bytes.fromhex(row[6])), bytes32(bytes.fromhex(row[5])), row[7] - ) + coin = Coin(bytes32(bytes.fromhex(row[6])), bytes32(bytes.fromhex(row[5])), row[7]) coins.add(CoinRecord(coin, row[1], row[2], row[3], row[4])) return list(coins) - async def rollback_to_block(self, block_index): + async def rollback_to_block(self, block_index: int): + """ + Note that block_index can be negative, in which case everything is rolled back + """ # Update memory cache delete_queue: bytes32 = [] for coin_name, coin_record in self.coin_record_cache.items(): - if coin_record.spent_block_index > block_index: + if int(coin_record.spent_block_index) > block_index: new_record = CoinRecord( coin_record.coin, coin_record.confirmed_block_index, @@ -130,16 +113,14 @@ class CoinStore: coin_record.coinbase, ) self.coin_record_cache[coin_record.coin.name().hex()] = new_record - if coin_record.confirmed_block_index > block_index: + if int(coin_record.confirmed_block_index) > block_index: delete_queue.append(coin_name) for coin_name in delete_queue: del self.coin_record_cache[coin_name] # Delete from storage - c1 = await self.coin_record_db.execute( - "DELETE FROM coin_record WHERE confirmed_index>?", (block_index,) - ) + c1 = await self.coin_record_db.execute("DELETE FROM coin_record WHERE confirmed_index>?", (block_index,)) await c1.close() c2 = await self.coin_record_db.execute( "UPDATE coin_record SET spent_index = 0, spent = 0 WHERE spent_index>?", @@ -150,15 +131,11 @@ class CoinStore: async def get_unspent_coin_records(self) -> List[CoinRecord]: coins = set() - cursor = await self.coin_record_db.execute( - "SELECT * from coin_record WHERE spent=0" - ) + cursor = await self.coin_record_db.execute("SELECT * from coin_record WHERE spent=0") rows = await cursor.fetchall() await cursor.close() for row in rows: - coin = Coin( - bytes32(bytes.fromhex(row[6])), bytes32(bytes.fromhex(row[5])), row[7] - ) + coin = Coin(bytes32(bytes.fromhex(row[6])), bytes32(bytes.fromhex(row[5])), row[7]) coins.add(CoinRecord(coin, row[1], row[2], row[3], row[4])) return list(coins)