def test_blockchain_is_valid_transaction_data_multiple_rewards(self): reward_transaction_1 = Transaction.reward_mining(Wallet()) reward_transaction_2 = Transaction.reward_mining(Wallet()) self.blockchain.add_block( [reward_transaction_1.info, reward_transaction_2.info]) err_message = 'Multiple mining rewards in the same block' with self.assertRaises(BlockchainError) as err: Blockchain.is_valid_transaction_data(self.blockchain.chain) self.assertIsInstance(err, BlockchainError) self.assertIn(err_message, err.message)
def test_blockchain_is_valid_transaction_invalid_historic_balance(self): wallet = Wallet() invalid_transaction = self._generate_transaction(wallet) invalid_transaction.output[wallet.address] = 1000 invalid_transaction.input['amount'] = 1001 invalid_transaction.input['signature'] = wallet.sign( invalid_transaction.output) self.blockchain.add_block([invalid_transaction.info]) err_message = 'historic balance inconsistency' with self.assertRaises(BlockchainError) as err: Blockchain.is_valid_transaction_data(self.blockchain.chain) self.assertIsInstance(err, BlockchainError) self.assertIn(err_message, err.message)
def _generate_transaction(self, sender: Wallet = None): recipient = uuid.uuid4().hex amount = random.randint(0, 100) sender = sender or Wallet() transaction = Transaction(sender=sender, recipient=recipient, amount=amount) return transaction
def test_blockchain_is_valid_transaction_data_invalid_transaction(self): invalid_transaction = self._generate_transaction() invalid_transaction.input['signature'] = Wallet().sign( invalid_transaction.output) self.blockchain.add_block([invalid_transaction.info]) err_message = 'Invalid transaction' with self.assertRaises(BlockchainError) as err: Blockchain.is_valid_transaction_data(self.blockchain.chain) self.assertIsInstance(err, BlockchainError) self.assertIn(err_message, err.message)
async def test_p2p_server_broadcast_transaction(self, mock_deserialize): transaction = Transaction(sender=Wallet(), recipient='recipient', amount=100) mock_deserialize.return_value = transaction nodes = [self.p2p_server.uri] self.p2p_server.nodes.uris.add(nodes) await self.p2p_server.start() await self.p2p_server.broadcast_transaction(transaction) self.assertTrue(mock_deserialize.called) self.p2p_server.close()
def test_transaction_update(self): recipient = 'recipient' balance = self.wallet.balance - self.amount amount = self._generate_float(ceil=balance) self.transaction.update(self.wallet, recipient, amount) self.assertEqual(self.transaction.output[recipient], amount) self.assertEqual(self.transaction.output[self.wallet.address], balance - amount) self.assertTrue( Wallet.verify(self.transaction.input['public_key'], self.transaction.input['signature'], self.transaction.output))
def test_transactions_pool_get_transaction(self): wallet = Wallet() output = {wallet.address: 10} input = {'address': wallet.address} uuid = Transaction.generate_uuid() initial_transaction = Transaction(uuid=uuid, input=input, output=output) address = initial_transaction.input.get('address') self.assertNotIn(initial_transaction.uuid, self.transactions_pool.pool.keys()) self.transactions_pool.add_transaction(initial_transaction) transaction = self.transactions_pool.get_transaction(address) self.assertEqual(transaction.uuid, initial_transaction.uuid)
def generate_input(self, sender: Wallet): """ Create input data structure for the transaction. :param Wallet sender: cryptocurrency sender wallet. :param dict output: transaction output. :return dict: transaction input data. """ input = {} input['timestamp'] = get_utcnow_timestamp() input['amount'] = sender.balance input['address'] = sender.address input['public_key'] = sender.public_key input['signature'] = sender.sign(self.output) return input
def is_valid_transaction_data(chain: list): """ Perform checks to enforce the consistnecy of transactions data in the chain blocks: Each transaction mush only appear once in the chain, there can only be one mining reward per block and each transaction must be valid. :param list chain: blockchain chain of blocks. :raise BlockchainError: on invalid transaction data. """ from src.client.models.transaction import Transaction from src.client.models.wallet import Wallet transaction_uuids = set() for index, block in enumerate(chain, start=0): has_reward = False for transaction_info in block.data: try: transaction = Transaction.create(**transaction_info) Transaction.is_valid(transaction) except TransactionError as err: message = f'Invalid transaction. {err.message}.' logger.error(f'[Blockchain] Validation error. {message}') raise BlockchainError(message) if transaction.uuid in transaction_uuids: message = f'Repetead transaction uuid found: {transaction.uuid}.' logger.error(f'[Blockchain] Validation error. {message}') raise BlockchainError(message) transaction_uuids.add(transaction.uuid) address = transaction.input.get('address') if address == MINING_REWARD_INPUT.get('address'): if has_reward: message = f'Multiple mining rewards in the same block: {block}.' logger.error( f'[Blockchain] Validation error. {message}') raise BlockchainError(message) has_reward = True else: historic_blockchain = Blockchain() historic_blockchain.chain = chain[:index] historic_balance = Wallet.get_balance( historic_blockchain, address) amount = transaction.input.get('amount') if historic_balance != amount: message = f'Address {address} historic balance inconsistency: {historic_balance} ({amount}).' logger.error( f'[Blockchain] Validation error. {message}') raise BlockchainError(message)
def valid_amount(cls, value: float, values: dict): """ Validate exchange amount value. :param float value: provided amount value. :param dict values: previous fields already validated. :return float: validated amount value. :raise ValueError: if exchange amount is less than sender balance. """ sender = values.get('sender', Wallet()) try: assert sender.balance > value except AssertionError: message = f'Amount {value} exceeds wallet balance {sender.balance}.' logger.error(f'[TransactionSchema] Validation error. {message}') raise ValueError(message) return value
def is_valid(transaction: Union[dict, 'Transaction']): """ Perform transaction logic validations. :param Transaction transaction: transaction instance to verify. :raise TransactionError: on transaction validation error. """ transaction = transaction if not isinstance( transaction, dict) else Transaction(**transaction) Transaction.is_valid_schema(transaction.info) if transaction.input.get('address') != MINING_REWARD_INPUT.get( 'address'): amount = sum(transaction.output.values()) if not transaction.input.get('amount') == amount: message = f'Invalid transaction amount: {amount}.' logger.error(f'[Transaction] Validation error. {message}') raise TransactionError(message) public_key = transaction.input.get('public_key') signature = transaction.input.get('signature') output = transaction.output if not Wallet.verify(public_key, signature, output): message = 'Invalid signature verification.' logger.error(f'[Transaction] Validation error. {message}') raise TransactionError(message)
def setUp(self): self.wallet = Wallet() self.wallet.blockchain = self._generate_blockchain() self.recipient = Wallet.generate_address() self.amount = self._generate_float(ceil=self.wallet.balance)
def _generate_address(self): return Wallet.generate_address()
def wallet(self) -> Wallet: if not hasattr(self, '_wallet'): self._wallet = Wallet(self.blockchain) return self._wallet
def test_transaction_is_valid_mining_reward(self, mock_is_valid_schema): mock_is_valid_schema.return_value = True wallet = Wallet() transaction = Transaction.reward_mining(wallet) Transaction.is_valid(transaction) self.assertTrue(mock_is_valid_schema.called)