Skip to content

Commit

Permalink
Bug fix + Added Unit Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cyyber committed Jun 3, 2022
1 parent 353b32a commit 76e1a54
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 58 deletions.
8 changes: 7 additions & 1 deletion src/qrl/core/Block.py
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
113 changes: 67 additions & 46 deletions src/qrl/core/ChainManager.py
Expand Up @@ -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
Expand All @@ -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:
"""
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/qrl/core/config.py
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
12 changes: 12 additions & 0 deletions src/qrl/core/p2p/p2pPeerManager.py
Expand Up @@ -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):
Expand Down
24 changes: 24 additions & 0 deletions src/qrl/core/txs/Transaction.py
Expand Up @@ -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
Expand Down
19 changes: 10 additions & 9 deletions tests/core/test_ChainManager.py
Expand Up @@ -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')
Expand Down

0 comments on commit 76e1a54

Please sign in to comment.