From 76e1a546c7ef04ef32a1b26beb28b2c21801398d Mon Sep 17 00:00:00 2001 From: cyyber Date: Fri, 3 Jun 2022 06:49:38 +0530 Subject: [PATCH] Bug fix + Added Unit Tests --- src/qrl/core/Block.py | 8 +- src/qrl/core/ChainManager.py | 113 +++++++++++++++++------------ src/qrl/core/config.py | 6 +- src/qrl/core/p2p/p2pPeerManager.py | 12 +++ src/qrl/core/txs/Transaction.py | 24 ++++++ tests/core/test_ChainManager.py | 19 ++--- 6 files changed, 124 insertions(+), 58 deletions(-) diff --git a/src/qrl/core/Block.py b/src/qrl/core/Block.py index f578df7ad..dc0d28b7c 100644 --- a/src/qrl/core/Block.py +++ b/src/qrl/core/Block.py @@ -6,7 +6,7 @@ from typing import Optional from google.protobuf.json_format import MessageToJson, Parse -from pyqrllib.pyqrllib import bin2hstr +from pyqrllib.pyqrllib import bin2hstr, hstr2bin from qrl.core.config import DevConfig from qrl.core.misc import logger, ntp @@ -215,6 +215,12 @@ def validate(self, chain_manager, future_blocks: OrderedDict) -> bool: if not coinbase_txn.validate_all(state_container): return False + if self.block_number != 2078158: + for proto_tx in self.transactions[1:]: + if proto_tx.WhichOneof('transactionType') == 'coinbase': + logger.warning("Multiple coinbase transaction found") + return False + except Exception as e: logger.warning('Exception %s', e) return False diff --git a/src/qrl/core/ChainManager.py b/src/qrl/core/ChainManager.py index 9b1eb3c21..31051db8d 100644 --- a/src/qrl/core/ChainManager.py +++ b/src/qrl/core/ChainManager.py @@ -524,57 +524,66 @@ def load(self, genesis_block): logger.warning("Migrated Block %s/%s", self.height, height) state_migration.state_migration_step_2(self._state) - logger.warning("Please Wait... Starting State Migration From Version 1 to %s", self._state.state_version) - height = self._state.get_mainchain_height() - start_block_number = 1 - logger.warning("Start blockheight %s", start_block_number) - total_block_reward = 0 - for block_number in range(start_block_number, height + 1): - total_block_reward += int(block_reward(block_number, config.dev)) - if block_number % 1000 == 0: - logger.warning("Migrated Block %s/%s", block_number, height) - - if self.height % 1000 != 0: - logger.warning("Migrated Block %s/%s", self.height, height) - - logger.warning('Please Wait... While verifying State') - total_balance = 0 - count = 0 - for address, _ in self._state._db.db: - address_state = None - if AddressState.address_is_valid(address): - address_state = OptimizedAddressState.get_optimized_address_state(self._state, address) - elif MultiSigAddressState.address_is_valid(address): - address_state = self.get_multi_sig_address_state(address) - - if not address_state: - continue + if state_version < 2: + logger.warning("Please Wait... Starting State Migration From Version 1 to %s", self._state.state_version) + height = self._state.get_mainchain_height() + start_block_number = 1 + logger.warning("Start blockheight %s", start_block_number) + total_block_reward = 0 + for block_number in range(start_block_number, height + 1): + total_block_reward += int(block_reward(block_number, config.dev)) + if block_number % 1000 == 0: + logger.warning("Migrated Block %s/%s", block_number, height) - count += 1 - total_balance += address_state.balance + if self.height % 1000 != 0: + logger.warning("Migrated Block %s/%s", self.height, height) - if count % 1000 == 0: - logger.warning("Processed Address %s", count) + logger.warning('Please Wait... While verifying State') + total_balance = 0 + count = 0 + for address, _ in self._state._db.db: + address_state = None + if AddressState.address_is_valid(address): + address_state = OptimizedAddressState.get_optimized_address_state(self._state, address) + elif MultiSigAddressState.address_is_valid(address): + address_state = self.get_multi_sig_address_state(address) + + if not address_state: + continue - if count % 1000 != 0: - logger.warning("Processed Address %s", count) + count += 1 + total_balance += address_state.balance - coinbase_balance = int(config.dev.coin_remaining_at_genesis * config.dev.shor_per_quanta - total_block_reward) - total_supply = total_balance + coinbase_balance - if total_supply != config.dev.max_coin_supply * config.dev.shor_per_quanta: - logger.warning('Total Supply: %s', total_supply) - logger.warning('Total Max Coin Supply: %s', config.dev.max_coin_supply * config.dev.shor_per_quanta) - raise Exception('Total supply mismatch, State Verification failed') + if count % 1000 == 0: + logger.warning("Processed Address %s", count) - a = OptimizedAddressState.get_optimized_address_state(self._state, config.dev.coinbase_address) - a.pbdata.balance = coinbase_balance - addresses_state = {config.dev.coinbase_address: a} - a.put_optimized_addresses_state(self._state, addresses_state) + if count % 1000 != 0: + logger.warning("Processed Address %s", count) - a = OptimizedAddressState.get_optimized_address_state(self._state, config.dev.coinbase_address) - if a.balance != coinbase_balance: - raise Exception('Unexpected Coinbase balance') - self._state.put_state_version() + coinbase_balance = int(config.dev.coin_remaining_at_genesis * config.dev.shor_per_quanta - total_block_reward) + total_supply = total_balance + coinbase_balance + if total_supply != config.dev.max_coin_supply * config.dev.shor_per_quanta: + logger.warning('Total Supply: %s', total_supply) + logger.warning('Total Max Coin Supply: %s', config.dev.max_coin_supply * config.dev.shor_per_quanta) + raise Exception('Total supply mismatch, State Verification failed') + + a = OptimizedAddressState.get_optimized_address_state(self._state, config.dev.coinbase_address) + a.pbdata.balance = coinbase_balance + addresses_state = {config.dev.coinbase_address: a} + a.put_optimized_addresses_state(self._state, addresses_state) + + a = OptimizedAddressState.get_optimized_address_state(self._state, config.dev.coinbase_address) + if a.balance != coinbase_balance: + raise Exception('Unexpected Coinbase balance') + self._state.put_state_version() + if state_version < 3: + # Adding extra block reward lost in block #2078158 + coinbase_addr = OptimizedAddressState.get_optimized_address_state(self._state, config.dev.coinbase_address) + coinbase_addr.pbdata.balance += int(block_reward(2078158, config.dev)) + addresses_state = {config.dev.coinbase_address: coinbase_addr} + coinbase_addr.put_optimized_addresses_state(self._state, addresses_state) + + self._state.put_state_version() def _update_chainstate(self, block: Block, batch): self._last_block = block @@ -583,6 +592,13 @@ def _update_chainstate(self, block: Block, batch): self._state.update_mainchain_height(block.block_number, batch) self._state.update_re_org_limit(block.block_number, batch) TransactionMetadata.update_tx_metadata(self._state, block, batch) + if block.block_number >= config.dev.hard_fork_heights[2]: + banned_addr = OptimizedAddressState.get_optimized_address_state(self._state, config.dev.banned_address[0]) + if banned_addr.pbdata.balance > 0: + banned_addr.pbdata.balance = 0 + addresses_state = {banned_addr.address: banned_addr} + banned_addr.put_optimized_addresses_state(self._state, addresses_state) + def _try_branch_add_block(self, block, dev_config: DevConfig, check_stale=True) -> bool: """ @@ -973,7 +989,12 @@ def _apply_state_changes(self, block, batch) -> bool: return False # Processing Rest of the Transaction - for proto_tx in block.transactions: + for index, proto_tx in enumerate(block.transactions): + if index > 0 and block.block_number != 2078158: + if proto_tx.WhichOneof('transactionType') == 'coinbase': + logger.warning("Multiple coinbase transaction found") + return False + tx = Transaction.from_pbdata(proto_tx) if not self.update_state_container(tx, state_container): return False diff --git a/src/qrl/core/config.py b/src/qrl/core/config.py index b3bfbf586..f34bc85f1 100644 --- a/src/qrl/core/config.py +++ b/src/qrl/core/config.py @@ -273,7 +273,7 @@ def __init__(self, pbdata, ignore_check=False, ignore_singleton=False): # STATE VERSION # ====================================== # Max number of data to be stored per key - self._state_version = 2 + self._state_version = 3 # ====================================== # STATE PAGINATION CONTROLLER @@ -311,13 +311,15 @@ def __init__(self, pbdata, ignore_check=False, ignore_singleton=False): # ====================================== # HARD FORK HEIGHTS LIST # ====================================== - self.hard_fork_heights = [942375, 1938000] + self.hard_fork_heights = [942375, 1938000, 2078800] + self.hard_fork_node_disconnect_delay = [0, 0, 2880] self.testnet_hard_fork_heights = [1, 3000] # ====================================== # PROPOSAL CONFIG # ====================================== self.proposal_unit_percentage = 100 + self.banned_address = [bytes(hstr2bin('010600fcd0db869d2e1b17b452bdf9848f6fe8c74ee5b8f935408cc558c601fb69eb553fa916a1'))] @property def pbdata(self): diff --git a/src/qrl/core/p2p/p2pPeerManager.py b/src/qrl/core/p2p/p2pPeerManager.py index a739aab72..b9be5ebcf 100644 --- a/src/qrl/core/p2p/p2pPeerManager.py +++ b/src/qrl/core/p2p/p2pPeerManager.py @@ -215,6 +215,18 @@ def _get_version_compatibility(self, version) -> bool: # logger.warning("Exception while checking version for compatibility") return True + hard_fork_2 = config.dev.hard_fork_heights[2] + config.dev.hard_fork_node_disconnect_delay[2] + if self._p2p_factory.chain_height >= hard_fork_2: + try: + major_version = version.split(".")[0] + if int(major_version) < 4: + return False + except Exception: + # Disabled warning as it is not required and could be annoying + # if a peer with dirty version is trying to connect with the node + # logger.warning("Exception while checking version for compatibility") + return True + return True def handle_version(self, source, message: qrllegacy_pb2.LegacyMessage): diff --git a/src/qrl/core/txs/Transaction.py b/src/qrl/core/txs/Transaction.py index c73f97e2c..9060f70bb 100644 --- a/src/qrl/core/txs/Transaction.py +++ b/src/qrl/core/txs/Transaction.py @@ -229,6 +229,30 @@ def validate(self, verify_signature=True) -> bool: return True def validate_all(self, state_container: StateContainer, check_nonce=True) -> bool: + if state_container.block_number >= state_container.current_dev_config.hard_fork_heights[2]: + for banned_address in state_container.current_dev_config.banned_address: + tx_type = self.pbdata.WhichOneof('transactionType') + addr_from_pk = None + if tx_type != 'coinbase': + addr_from_pk = bytes(QRLHelper.getAddress(self.PK)) + + if addr_from_pk == banned_address or self.master_addr == banned_address: + logger.warning("Banned QRL Address found in master_addr or pk") + return False + if tx_type == 'coinbase': + if self.pbdata.coinbase.addr_to == banned_address: + logger.warning("Banned QRL Address found in coinbase.addr_to") + return False + elif tx_type == 'message': + if self.pbdata.message.addr_to == banned_address: + logger.warning("Banned QRL Address found in message.addr_to") + return False + elif tx_type == 'transfer': + for addr_to in self.pbdata.transfer.addrs_to: + if banned_address == addr_to: + logger.warning("Banned QRL Address found in transfer.addr_to") + return False + if self.pbdata.WhichOneof('transactionType') == 'coinbase': if not self._validate_extended(state_container): return False diff --git a/tests/core/test_ChainManager.py b/tests/core/test_ChainManager.py index dd1986b65..2290abb28 100644 --- a/tests/core/test_ChainManager.py +++ b/tests/core/test_ChainManager.py @@ -112,33 +112,34 @@ def test_load_twice(self): self.chain_manager._fork_recovery.assert_called_with(self.genesis_block, m_fork_state) @patch('qrl.core.misc.ntp.getTime') - def test_simple_add_block(self, time_mock): + def test_simple_add_block_multiple_coinbase(self, time_mock): # Simply test that adding a block on top of the genesis block works. with patch.object(DifficultyTracker, 'get', return_value=ask_difficulty_tracker('2', config.dev)): self.chain_manager.load(self.genesis_block) time_mock.return_value = 1615270948 # Very high to get an easy difficulty - + from qrl.core.txs.CoinBase import CoinBase + exploit_tx = CoinBase.create(config.dev, 250000000, alice.address, 1) + exploit_tx.pbdata.signature = b'00000000' block_1 = Block.create(dev_config=config.dev, block_number=1, prev_headerhash=self.genesis_block.headerhash, prev_timestamp=self.genesis_block.timestamp, - transactions=[], + transactions=[exploit_tx], miner_address=alice.address, seed_hash=None, seed_height=None) - block_1.set_nonces(config.dev, 201, 0) + block_1.set_nonces(config.dev, 204, 0) # Uncomment only to determine the correct mining_nonce of above blocks # from qrl.core.PoWValidator import PoWValidator - # while not PoWValidator().validate_mining_nonce(self.state, block_1.blockheader, False): - # block_1.set_nonces(block_1.mining_nonce + 1) + # while not self.chain_manager.validate_mining_nonce(block_1.blockheader, config.dev, False): + # block_1.set_nonces(config.dev, block_1.mining_nonce + 1, 0) # print(block_1.mining_nonce) - self.assertTrue(block_1.validate(self.chain_manager, {})) + self.assertFalse(block_1.validate(self.chain_manager, {})) result = self.chain_manager.add_block(block_1) - self.assertTrue(result) - self.assertEqual(self.chain_manager.last_block, block_1) + self.assertFalse(result) @set_default_balance_size() @patch('qrl.core.misc.ntp.getTime')