import logging import unittest from datetime import datetime from unittest.mock import patch, MagicMock from freezegun import freeze_time from aucoin import consensus, dsa, util from aucoin.block import Block from aucoin.blockchain import Blockchain from aucoin.database import session_scope from aucoin.exceptions import InvalidBlockException, OrphanException from aucoin.mempool import Mempool from aucoin.network import Network from aucoin.transactions import CoinbaseTransaction from aucoin.validation import Validator from aucoin.wallet import public_bytes from tests import helpers logging.basicConfig(level=logging.DEBUG) easy_target = bytes.fromhex("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") @patch("aucoin.consensus.required_target", MagicMock(return_value=easy_target)) class TestAddBlock(unittest.TestCase): def setUp(self): # setup a fresh blockchain and mempool self.blockchain = Blockchain(reset=True) self.mempool = Mempool() self.network = Network(self.blockchain, self.mempool, max_peers=0) # A valid block used for testing. with session_scope() as session: self.private_key, self.public_key = dsa.generate_keypair() self.block = Block( target=easy_target, hash_prev_block=self.blockchain.genesis_block(session).hash, public_key=public_bytes(self.public_key), transactions=[ CoinbaseTransaction( address=util.address(public_bytes(self.public_key)), block_height=1 ) ] ) self.validator = Validator(helpers.Core(), self.blockchain, self.mempool, self.network) def test_valid(self): self.validator.add_block(helpers.mine(self.block, self.private_key)) def test_reject_invalid_syntax(self): self.block.transactions = [] self.assertRaisesRegex(InvalidBlockException, "Transaction list must be non-empty", self.validator.add_block, helpers.mine(self.block, self.private_key)) def test_reject_duplicate(self): self.block = helpers.mine(self.block, self.private_key) self.validator.add_block(self.block) self.assertRaisesRegex(InvalidBlockException, "Already exists in blockchain", self.validator.add_block, self.block) def test_reject_orphan(self): self.block.hash_prev_block = b"non existent" with self.assertRaises(OrphanException) as cm: self.validator.add_block(helpers.mine(self.block, self.private_key)) self.assertEqual(cm.exception.missing, b'non existent') def test_reject_target_difficulty_rules_mismatch(self): self.block.target = bytes.fromhex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") self.assertRaisesRegex(InvalidBlockException, "Target does not match the difficulty rules", self.validator.add_block, helpers.mine(self.block, self.private_key)) @freeze_time("2010-01-01") def test_reject_future_timestamp(self): self.block.timestamp = int((datetime(2010, 1, 1, 0, 0, 1) + consensus.block_max_future_time).timestamp()) self.assertRaisesRegex(InvalidBlockException, "Block timestamp must not be more than block_max_future_time in the future", self.validator.add_block, helpers.mine(self.block, self.private_key)) def test_reject_old_timestamp(self): with session_scope() as session: self.block.timestamp = self.blockchain.genesis_block(session).timestamp self.assertRaisesRegex(InvalidBlockException, "Timestamp is before or equal to median time of the last n blocks", self.validator.add_block, helpers.mine(self.block, self.private_key)) def test_reject_incorrect_block_height(self): self.block.transactions[0].inputs[0].block_height = 3 self.block.calculate_merkle() self.assertRaisesRegex(InvalidBlockException, "Block height is not equal to previous block's height \+ 1", self.validator.add_block, helpers.mine(self.block, self.private_key))