class TestMiner(TestCase): def setUp(self): self.time = 1526830525 self.m_mining_qaddress = alice.qaddress self.m_mining_address = parse_qaddress(self.m_mining_qaddress) self.chain_manager = Mock(spec=ChainManager) self.parent_block = Block() self.parent_difficulty = StringToUInt256( '0') # tuple (0,0,0,0,0...) length 32 self.m_pre_block_logic = Mock(spec=POW.pre_block_logic, name='hello') m_add_unprocessed_txn_fn = create_autospec( P2PFactory.add_unprocessed_txn) mining_thread_count = 1 self.miner = Miner(self.m_pre_block_logic, self.m_mining_address, self.chain_manager, mining_thread_count, m_add_unprocessed_txn_fn) self.txpool = Mock(spec=TransactionPool) self.txpool.transactions = [] def test_prepare_next_unmined_block_template_works(self, m_getTime, m_logger): """ All the setup stuff you need before you actually mine a block goes here. It's broken out into this function, because if you have a mining pool, this function prepares the getblocktemplate for the pool. """ m_getTime.return_value = self.time self.chain_manager.get_measurement.return_value = 60 self.txpool.transactions = [] self.assertIsNone(self.miner._current_difficulty) self.assertIsNone(self.miner._current_target) self.assertIsNone(self.miner._measurement) self.miner.prepare_next_unmined_block_template(self.m_mining_address, self.txpool, self.parent_block, self.parent_difficulty) self.assertEqual(self.miner._current_difficulty, StringToUInt256('2')) self.assertEqual( self.miner._current_target, StringToUInt256( '115792089237316195423570985008687907853269984665640564039457584007913129639807' )) self.assertEqual(self.miner._measurement, 60) # because we set it earlier in this test def test_prepare_next_unmined_block_template_exception( self, m_getTime, m_logger): """ If this function should throw an exception, nothing should happen except a call to the logger. """ m_getTime.return_value = self.time self.chain_manager.get_measurement.side_effect = ValueError self.txpool.transactions = [] self.assertIsNone(self.miner._current_difficulty) self.assertIsNone(self.miner._current_target) self.assertIsNone(self.miner._measurement) self.miner.prepare_next_unmined_block_template(self.m_mining_address, self.txpool, self.parent_block, self.parent_difficulty) self.assertIsNone(self.miner._current_difficulty) self.assertIsNone(self.miner._current_target) self.assertIsNone(self.miner._measurement) m_logger.warning.assert_called_once() m_logger.exception.assert_called_once() def test_start_mining_works(self, m_getTime, m_logger): m_getTime.return_value = self.time # Do prepare_next_unmined_block_template()'s job self.miner._mining_block = Block() # From sample run of test_prepare_next_unmined_block_template_works() self.miner._measurement = 60 self.miner._current_difficulty = StringToUInt256('0') self.miner._current_target = \ StringToUInt256('115792089237316195423570985008687907853269984665640564039457584007913129639807') # start() is from Qryptominer, let's not actually mine in a test with patch('qrl.core.Miner.Miner.start', spec=True) as m_start: self.miner.start_mining(self.parent_block, self.parent_difficulty) m_start.assert_called_once() def create_block_with_transactions(self, m_logger): self.miner.create_block(last_block=self.parent_block, mining_nonce=0, tx_pool=self.txpool, miner_address=self.m_mining_address) def test_get_block_to_mine_no_existing_block_being_mined_upon( self, m_getTime, m_logger): """ This function takes a Qaddress, and returns a blob for the miner/mining pool to work on. It makes sure we have a block we're trying to mine and that the coinbase points to the Qaddress. In this test we check that: 1. If we don't have a block we're trying to mine on, it generates one on the fly. """ m_getTime.return_value = 1526830525 self.miner._current_difficulty = StringToUInt256('1') blob, difficulty = self.miner.get_block_to_mine( self.m_mining_qaddress.encode(), self.txpool, self.parent_block, self.parent_difficulty) self.assertEqual( difficulty, 1) # because self.miner._current_difficulty was set above self.assertEqual( blob, '0014db80611fbf16e342a2afb8b77b1f513f9db21de3ff905c0c27ea0078c489248f37f9e2a22400000000000000000000000000000000004bfaabbf147f985be702a373183be1be77100b24' ) # noqa def test_get_block_to_mine_not_mining_upon_last_block( self, m_getTime, m_logger): """ In this test we check that: 2. If we aren't mining upon the last block, it regenerates the blocktemplate. """ m_getTime.return_value = 1526830525 self.miner._current_difficulty = StringToUInt256('1') m_mining_block = Mock(autospec=Block) m_mining_block.mining_blob = b'big_bad_blob' m_mining_block.prev_headerhash = b'nothing should be equal to this' self.miner._mining_block = m_mining_block blob, difficulty = self.miner.get_block_to_mine( self.m_mining_qaddress.encode(), self.txpool, self.parent_block, self.parent_difficulty) self.assertEqual( difficulty, 1) # because self.miner._current_difficulty was set above self.assertEqual( blob, '0014db80611fbf16e342a2afb8b77b1f513f9db21de3ff905c0c27ea0078c489248f37f9e2a22400000000000000000000000000000000004bfaabbf147f985be702a373183be1be77100b24' ) # noqa def test_get_block_to_mine_perfect_block_no_changes( self, m_getTime, m_logger): """ In this test, we check that the function makes no changes to the block if the coinbase addr is our addr, and we are on the latest block. """ m_coinbase = Mock(autospec=CoinBase, name='I am a Coinbase') m_coinbase.coinbase.addr_to = self.m_mining_address m_parent_block = Mock(autospec=Block, name='mock parent_block') m_parent_block.transactions = [m_coinbase] m_mining_block = Mock(autospec=Block, name='mock _mining_block') m_mining_block.transactions = [m_coinbase] m_mining_block.mining_blob = b'this is the blob you should iterate the nonce upon' self.miner._mining_block = m_mining_block self.miner._current_difficulty = StringToUInt256('1') m_parent_block.headerhash = b'block_headerhash' m_mining_block.prev_headerhash = b'block_headerhash' blob, difficulty = self.miner.get_block_to_mine( self.m_mining_qaddress.encode(), self.txpool, m_parent_block, self.parent_difficulty) self.assertEqual( blob, '746869732069732074686520626c6f6220796f752073686f756c64206974657261746520746865206e6f6e63652075706f6e' ) self.assertEqual(difficulty, 1) def test_get_block_to_mine_we_have_a_block_in_mind(self, m_getTime, m_logger): """ In this test, we check that 1. The function checks that the blocktemplate's coinbase points to the given Qaddress 2. The function checks that we are mining on the tip :param m_logger: :return: """ m_coinbase = Mock(autospec=CoinBase, name='I am a Coinbase') m_coinbase.coinbase.addr_to = self.m_mining_address m_parent_block = Mock(autospec=Block, name='mock parent_block') m_parent_block.transactions = [m_coinbase] m_mining_block = Mock(autospec=Block, name='mock _mining_block') m_mining_block.transactions = [m_coinbase] m_mining_block.mining_blob = b'this is the blob you should iterate the nonce upon' self.miner._mining_block = m_mining_block self.miner._current_difficulty = StringToUInt256('1') # If the coinbase doesn't point to us, make it point to us. foreign_qaddress = bob.qaddress m_parent_block.headerhash = b'block_headerhash' m_mining_block.prev_headerhash = b'block_headerhash' blob, difficulty = self.miner.get_block_to_mine( foreign_qaddress.encode(), self.txpool, m_parent_block, self.parent_difficulty) # actually, the blob's value will not change because mining_block.update_mining_address() is a mock. # it will have the same value as in test_get_block_to_mine_perfect_block_no_changes() # it's enough to see that it actually runs m_mining_block.update_mining_address.assert_called_once() self.assertIsNotNone(blob) self.assertEqual(difficulty, 1) def test_submit_mined_block(self, m_getTime, m_logger): """ This runs when a miner submits a blob with a valid nonce. It returns True only if BlockHeader says the nonce position is okay, and the PoWValidator says the nonce is valid. :param m_getTime: :param m_logger: :return: """ m_mining_block = Mock(autospec=Block, name='mock _mining_block') m_mining_block.verify_blob.return_value = False self.miner._mining_block = m_mining_block blob = 'this is a blob12345that was the nonce'.encode() result = self.miner.submit_mined_block(blob) self.assertFalse(result) m_mining_block.verify_blob.return_value = True self.chain_manager.validate_mining_nonce = MagicMock( return_value=False) result = self.miner.submit_mined_block(blob) self.assertFalse(result) m_mining_block.verify_blob.return_value = True self.chain_manager.validate_mining_nonce = MagicMock(return_value=True) self.m_pre_block_logic.return_value = True result = self.miner.submit_mined_block(blob) self.assertTrue(result)
class TestMinerWithRealTransactionPool(TestCase): def setUp(self): self.m_mining_qaddress = alice.qaddress self.m_mining_address = parse_qaddress(self.m_mining_qaddress) self.chain_manager = Mock(spec=ChainManager) self.chain_manager.get_block_size_limit.return_value = 500 self.chain_manager.get_address_state.return_value = Mock(autospec=AddressState, name='mock alice AddressState') self.parent_block = Block() self.parent_difficulty = StringToUInt256('0') # tuple (0,0,0,0,0...) length 32 self.m_pre_block_logic = Mock(spec=POW.pre_block_logic, name='hello') m_add_unprocessed_txn_fn = create_autospec(P2PFactory.add_unprocessed_txn) mining_thread_count = 1 self.miner = Miner(self.m_pre_block_logic, self.m_mining_address, self.chain_manager, mining_thread_count, m_add_unprocessed_txn_fn) self.txpool = TransactionPool(None) def replacement_set_affected_address(addresses_set): return addresses_set.add(alice.address) self.m_tx_args = {"addr_from": alice.address, "addrs_to": [bob.address], "amounts": [10], "fee": 1, "PK": alice.pk, "master_addr": None, "size": 150, "validate_extended.return_value": True, "set_affected_address": replacement_set_affected_address } @patch('qrl.core.Miner.Block.create') def test_create_block_with_one_transaction(self, m_create, m_logger): m_tx = Mock(autospec=TransferTransaction, name='mock TransferTransaction') m_tx.configure_mock(**self.m_tx_args) m_create.return_value = Mock(autospec=Block, name='mock Block', size=205) self.txpool.add_tx_to_pool(m_tx, 1, replacement_getTime()) self.miner.create_block(last_block=self.parent_block, mining_nonce=0, tx_pool=self.txpool, miner_address=self.m_mining_address) m_create.assert_called_with(block_number=1, prev_headerhash=b'', prev_timestamp=0, transactions=[m_tx], miner_address=alice.address) @patch('qrl.core.Miner.Block.create') def test_create_block_does_not_include_invalid_txs_from_txpool(self, m_create, m_logger): self.m_tx_args["validate_extended.return_value"] = False m_tx = Mock(autospec=TransferTransaction, name='mock TransferTransaction') m_tx.configure_mock(**self.m_tx_args) m_create.return_value = Mock(autospec=Block, name='mock Block', size=205) self.txpool.add_tx_to_pool(m_tx, 1, replacement_getTime()) self.miner.create_block(last_block=self.parent_block, mining_nonce=0, tx_pool=self.txpool, miner_address=self.m_mining_address) m_create.assert_called_with(block_number=1, prev_headerhash=b'', prev_timestamp=0, transactions=[], miner_address=alice.address)
class TestMinerWithRealTransactionPool(TestCase): def setUp(self): self.m_mining_qaddress = alice.qaddress self.m_mining_address = parse_qaddress(self.m_mining_qaddress) self.alice_address_state = Mock( autospec=OptimizedAddressState, name='mock alice OptimizedAddressState') self.chain_manager = Mock(spec=ChainManager) self.chain_manager.get_block_size_limit.return_value = 500 self.chain_manager.get_address_state.return_value = self.alice_address_state self.chain_manager.get_config_by_block_number.return_value = config.dev self.parent_block = Block() self.parent_difficulty = StringToUInt256( '0') # tuple (0,0,0,0,0...) length 32 self.m_pre_block_logic = Mock(spec=POW.pre_block_logic, name='hello') mining_thread_count = 1 self.miner = Miner(self.chain_manager, self.m_pre_block_logic, self.m_mining_address, mining_thread_count) self.txpool = TransactionPool(None) def replacement_set_affected_address(addresses_set): return addresses_set.add(alice.address) self.m_tx_args = { "addr_from": alice.address, "addrs_to": [bob.address], "amounts": [10], "fee": 1, "PK": alice.pk, "master_addr": None, "size": 150, "validate_extended.return_value": True, "set_affected_address": replacement_set_affected_address } def mock_new_state_container(self): addresses_state = {alice.address: self.alice_address_state} self.chain_manager.new_state_container.return_value = StateContainer( addresses_state=addresses_state, tokens=Indexer(b'token', None), slaves=Indexer(b'slave', None), lattice_pk=Indexer(b'lattice_pk', None), multi_sig_spend_txs=dict(), votes_stats=dict(), block_number=5, total_coin_supply=0, current_dev_config=config.dev, write_access=False, my_db=None, batch=None) @patch('qrl.core.Miner.Block.create') def test_create_block_with_one_transaction(self, m_create, m_logger): self.mock_new_state_container() m_tx = Mock(autospec=TransferTransaction, name='mock TransferTransaction') m_tx.fee = 0 m_tx.configure_mock(**self.m_tx_args) m_create.return_value = Mock(autospec=Block, name='mock Block', size=205) self.txpool.add_tx_to_pool(m_tx, 1, replacement_getTime()) self.miner.create_block(last_block=self.parent_block, mining_nonce=0, tx_pool=self.txpool, miner_address=self.m_mining_address) seed_block = self.chain_manager.get_block_by_number( self.miner._qn.get_seed_height(self.parent_block.block_number + 1)) m_create.assert_called_with(dev_config=config.dev, block_number=1, prev_headerhash=b'', prev_timestamp=0, transactions=[m_tx], miner_address=alice.address, seed_height=seed_block.block_number, seed_hash=seed_block.headerhash) @patch('qrl.core.Miner.Block.create') def test_create_block_does_not_include_invalid_txs_from_txpool( self, m_create, m_logger): self.mock_new_state_container() m_tx = Mock(autospec=TransferTransaction, name='mock TransferTransaction') m_tx.validate_all.return_value = False m_tx.configure_mock(**self.m_tx_args) m_create.return_value = Mock(autospec=Block, name='mock Block', size=205) self.txpool.add_tx_to_pool(m_tx, 1, replacement_getTime()) self.miner.create_block(last_block=self.parent_block, mining_nonce=0, tx_pool=self.txpool, miner_address=self.m_mining_address) seed_block = self.chain_manager.get_block_by_number( self.miner._qn.get_seed_height(self.parent_block.block_number + 1)) m_create.assert_called_with(dev_config=config.dev, block_number=1, prev_headerhash=b'', prev_timestamp=0, transactions=[], miner_address=alice.address, seed_height=seed_block.block_number, seed_hash=seed_block.headerhash)