Beispiel #1
0
 def setUp(self):
     super(TransactionTest, self).setUp()
     self.transaction = Transaction(sender=self.wallet,
                                    recipient=self.recipient,
                                    amount=self.amount)
     self.transaction_info = self.transaction.info
     self.transaction_attrs = self.transaction_info.keys()
Beispiel #2
0
 def test_transaction_is_valid_invalid_output(self, mock_is_valid_schema):
     mock_is_valid_schema.return_value = True
     self.transaction.output[self.wallet.address] = self._generate_float()
     with self.assertRaises(TransactionError) as err:
         Transaction.is_valid(self.transaction)
         self.assertTrue(mock_is_valid_schema.called)
         self.assertIsInstance(err, TransactionError)
         self.assertIn('Invalid transaction amount', err.message)
Beispiel #3
0
 def test_transaction_create_invalid_schema(self, mock_is_valid_schema):
     err_message = 'Validation error'
     mock_is_valid_schema.side_effect = Mock(
         side_effect=TransactionError(err_message))
     with self.assertRaises(TransactionError) as err:
         Transaction.create(**self.transaction_info)
         self.assertTrue(mock_is_valid_schema.called)
         self.assertIsInstance(err, TransactionError)
         self.assertIn(err_message, err.message)
Beispiel #4
0
 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)
Beispiel #5
0
 def test_transaction_is_valid_invalid_signature(self,
                                                 mock_is_valid_schema):
     mock_is_valid_schema.return_value = True
     self.transaction.output[self.wallet.address] = self._generate_float()
     self.transaction.input['signature'] = self.wallet.sign(
         self.transaction.output)
     with self.assertRaises(TransactionError) as err:
         Transaction.is_valid(self.transaction)
         self.assertTrue(mock_is_valid_schema.called)
         self.assertIsInstance(err, TransactionError)
         self.assertIn('Invalid signature verification', err.message)
Beispiel #6
0
 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)
Beispiel #7
0
    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)
Beispiel #8
0
 def test_transaction_generate_output(self):
     output = Transaction.generate_output(self.wallet, self.recipient,
                                          self.amount)
     self.assertIsInstance(output, dict)
     self.assertTrue(all([isinstance(key, str) for key in output.keys()]))
     self.assertTrue(
         all([isinstance(value, float) for value in output.values()]))
Beispiel #9
0
 def test_transaction_generate_output_same_address(self):
     output = Transaction.generate_output(self.wallet, self.wallet.address,
                                          self.amount)
     self.assertIsInstance(output, dict)
     self.assertTrue(len(output.keys()), 1)
     self.assertEqual(output.get(self.wallet.address), self.wallet.balance)
     self.assertNotEqual(output.get(self.wallet.address), self.amount)
Beispiel #10
0
 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
Beispiel #11
0
async def mine_block():
    logger.info('[API] GET mine. Mining block.')
    transaction_reward = Transaction.reward_mining(router.wallet)
    data = router.transactions_pool.data
    data.append(transaction_reward.info)
    block = router.blockchain.add_block(data)
    logger.info(f'[API] GET mine. Block mined: {block}.')
    await router.p2p_server.broadcast_chain()
    router.transactions_pool.clear_pool(router.blockchain)
    return {'block': block}
Beispiel #12
0
 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()
Beispiel #13
0
    def deserialize(cls, pool: list):
        """
        Create a new transactions pool instance from the provided serialized pool.

        :param list pool: serialized pool of transactions.
        :return TransactionsPool: transactions pool created from provided stringified pool.
        """
        transactions = list(
            map(lambda transaction: Transaction.deserialize(transaction),
                pool))
        pool = {transaction.uuid: transaction for transaction in transactions}
        return cls(pool)
Beispiel #14
0
    async def _send_transaction(self, socket: Socket,
                                transaction: Transaction):
        """
        Send message with new transaction data over a socket connection.

        :param Socket socket: outgoing socket client.
        :param Transaction transaction: transaction instance to send.
        """
        message = {
            'channel': CHANNELS.get('transact'),
            'content': transaction.serialize()
        }
        await self._send(socket, message)
Beispiel #15
0
 def test_transaction_create_valid_schema(self, mock_is_valid_schema):
     mock_is_valid_schema.return_value = True
     transaction = Transaction.create(**self.transaction_info)
     self.assertTrue(mock_is_valid_schema.called)
     self.assertIsInstance(transaction, Transaction)
     self.assertTrue(
         all([
             attr in self.transaction.info.keys()
             for attr in self.transaction_info.keys()
         ]))
     self.assertTrue(
         all([
             value in self.transaction.info.values()
             for value in self.transaction_info.values()
         ]))
Beispiel #16
0
async def transact(data: dict):
    logger.info('[API] POST transact. New transaction.')
    recipient = data.get('recipient')
    amount = data.get('amount')
    transaction = router.transactions_pool.get_transaction(
        router.wallet.address)
    if transaction:
        transaction.update(router.wallet, recipient, amount)
        logger.info(
            f'[API] POST transact. Transaction updated: {transaction}.')
    else:
        transaction = Transaction.create(sender=router.wallet,
                                         recipient=recipient,
                                         amount=amount)
        logger.info(f'[API] POST transact. Transaction made: {transaction}.')
    router.transactions_pool.add_transaction(transaction)
    await router.p2p_server.broadcast_transaction(transaction)
    return {'transaction': transaction}
Beispiel #17
0
    async def _message_handler(self, socket: Socket):
        """
        Handle the incoming socket client and process the message data.
        It exits normally when the connection is closed.

        :param Socket socket: incoming socket client.
        """
        async for message in socket:
            data = parse(message)
            channel = data.get('channel')
            if channel == CHANNELS.get('node'):
                uri = data.get('content')
                info_msg = f'Uri listed. {uri}.'
                logger.info(f'[P2PServer] Node received. {info_msg}')
                self.add_uris(uri)
                await self._connect_socket(self._send_chain, uri)
            elif channel == CHANNELS.get('sync'):
                uris = data.get('content')
                self.add_uris(uris)
                info_msg = f'Total uris: {self.nodes.uris.array}.'
                logger.info(
                    f'[P2PServer] Synchronization finished. {info_msg}')
            elif channel == CHANNELS.get('chain'):
                chain = data.get('content')
                logger.info(f'[P2PServer] Chain received. {chain}.')
                blockchain = Blockchain.deserialize(chain)
                self.blockchain.set_valid_chain(blockchain.chain)
                self.transactions_pool.clear_pool(self.blockchain)
            elif channel == CHANNELS.get('transact'):
                transaction_info = data.get('content')
                logger.info(
                    f'[P2PServer] Transaction received. {transaction_info}.')
                transaction = Transaction.deserialize(transaction_info)
                self.transactions_pool.add_transaction(transaction)
            else:
                error_msg = f'Unknown channel received: {channel}.'
                logger.error(f'[P2PServer] Channel error. {error_msg}')
Beispiel #18
0
 def test_transaction_reward_mining(self):
     transaction = Transaction.reward_mining(self.wallet)
     self.assertEqual(transaction.input, MINING_REWARD_INPUT)
     self.assertEqual(transaction.output[self.wallet.address],
                      MINING_REWARD)
Beispiel #19
0
 def test_transaction_is_valid_schema(self):
     Transaction.is_valid_schema(self.transaction_info)
Beispiel #20
0
 def test_transaction_deserialize(self):
     serialized = self.transaction.serialize()
     self.assertIsInstance(Transaction.deserialize(serialized), Transaction)
Beispiel #21
0
 def test_transaction_is_valid_schema_validation_error(self):
     self.transaction_info.pop('input')
     with self.assertRaises(TransactionError) as err:
         Transaction.is_valid_schema(self.transaction_info)
         self.assertIsInstance(err, TransactionError)
         self.assertIn('Validation error', err.message)
Beispiel #22
0
 def test_transaction_is_valid(self, mock_is_valid_schema):
     mock_is_valid_schema.return_value = True
     Transaction.is_valid(self.transaction)
     self.assertTrue(mock_is_valid_schema.called)
Beispiel #23
0
 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)
Beispiel #24
0
class TransactionTest(ClientMixin):
    def setUp(self):
        super(TransactionTest, self).setUp()
        self.transaction = Transaction(sender=self.wallet,
                                       recipient=self.recipient,
                                       amount=self.amount)
        self.transaction_info = self.transaction.info
        self.transaction_attrs = self.transaction_info.keys()

    def test_transaction_string_representation(self):
        self.assertTrue(
            all([
                attr in str(self.transaction)
                for attr in self.transaction_attrs
            ]))

    def test_transaction_generate_uuid(self):
        id = Transaction.generate_uuid()
        self.assertTrue(re.match(r'^[0-9]{,39}$', str(id)))
        self.assertIsInstance(uuid.UUID(int=id), uuid.UUID)

    def test_transaction_generate_output(self):
        output = Transaction.generate_output(self.wallet, self.recipient,
                                             self.amount)
        self.assertIsInstance(output, dict)
        self.assertTrue(all([isinstance(key, str) for key in output.keys()]))
        self.assertTrue(
            all([isinstance(value, float) for value in output.values()]))

    def test_transaction_generate_output_same_address(self):
        output = Transaction.generate_output(self.wallet, self.wallet.address,
                                             self.amount)
        self.assertIsInstance(output, dict)
        self.assertTrue(len(output.keys()), 1)
        self.assertEqual(output.get(self.wallet.address), self.wallet.balance)
        self.assertNotEqual(output.get(self.wallet.address), self.amount)

    def test_transaction_generate_input(self):
        keys = ('timestamp', 'amount', 'address', 'public_key', 'signature')
        input = self.transaction.generate_input(self.wallet)
        self.assertIsInstance(input, dict)
        self.assertTrue(all([key in keys for key in input.keys()]))

    def test_transaction_info_property(self):
        attrs = self.transaction.__dict__
        for attr in attrs:
            self.assertIn(attr, self.transaction_info)

    def test_transaction_serialize(self):
        serialized = self.transaction.serialize()
        self.assertIsInstance(serialized, str)
        self.assertTrue(
            all([attr in serialized for attr in self.transaction_attrs]))

    def test_transaction_deserialize(self):
        serialized = self.transaction.serialize()
        self.assertIsInstance(Transaction.deserialize(serialized), Transaction)

    @patch.object(Transaction, 'is_valid_schema')
    def test_transaction_create_valid_schema(self, mock_is_valid_schema):
        mock_is_valid_schema.return_value = True
        transaction = Transaction.create(**self.transaction_info)
        self.assertTrue(mock_is_valid_schema.called)
        self.assertIsInstance(transaction, Transaction)
        self.assertTrue(
            all([
                attr in self.transaction.info.keys()
                for attr in self.transaction_info.keys()
            ]))
        self.assertTrue(
            all([
                value in self.transaction.info.values()
                for value in self.transaction_info.values()
            ]))

    @patch.object(Transaction, 'is_valid_schema')
    def test_transaction_create_invalid_schema(self, mock_is_valid_schema):
        err_message = 'Validation error'
        mock_is_valid_schema.side_effect = Mock(
            side_effect=TransactionError(err_message))
        with self.assertRaises(TransactionError) as err:
            Transaction.create(**self.transaction_info)
            self.assertTrue(mock_is_valid_schema.called)
            self.assertIsInstance(err, TransactionError)
            self.assertIn(err_message, err.message)

    def test_transaction_is_valid_schema(self):
        Transaction.is_valid_schema(self.transaction_info)

    def test_transaction_is_valid_schema_validation_error(self):
        self.transaction_info.pop('input')
        with self.assertRaises(TransactionError) as err:
            Transaction.is_valid_schema(self.transaction_info)
            self.assertIsInstance(err, TransactionError)
            self.assertIn('Validation error', err.message)

    @patch.object(Transaction, 'is_valid_schema')
    def test_transaction_is_valid(self, mock_is_valid_schema):
        mock_is_valid_schema.return_value = True
        Transaction.is_valid(self.transaction)
        self.assertTrue(mock_is_valid_schema.called)

    @patch.object(Transaction, 'is_valid_schema')
    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)

    @patch.object(Transaction, 'is_valid_schema')
    def test_transaction_is_valid_invalid_output(self, mock_is_valid_schema):
        mock_is_valid_schema.return_value = True
        self.transaction.output[self.wallet.address] = self._generate_float()
        with self.assertRaises(TransactionError) as err:
            Transaction.is_valid(self.transaction)
            self.assertTrue(mock_is_valid_schema.called)
            self.assertIsInstance(err, TransactionError)
            self.assertIn('Invalid transaction amount', err.message)

    @patch.object(Transaction, 'is_valid_schema')
    def test_transaction_is_valid_invalid_signature(self,
                                                    mock_is_valid_schema):
        mock_is_valid_schema.return_value = True
        self.transaction.output[self.wallet.address] = self._generate_float()
        self.transaction.input['signature'] = self.wallet.sign(
            self.transaction.output)
        with self.assertRaises(TransactionError) as err:
            Transaction.is_valid(self.transaction)
            self.assertTrue(mock_is_valid_schema.called)
            self.assertIsInstance(err, TransactionError)
            self.assertIn('Invalid signature verification', err.message)

    def test_transaction_reward_mining(self):
        transaction = Transaction.reward_mining(self.wallet)
        self.assertEqual(transaction.input, MINING_REWARD_INPUT)
        self.assertEqual(transaction.output[self.wallet.address],
                         MINING_REWARD)

    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_transaction_update_invalid_amount(self):
        amount = self.wallet.balance
        err_message = 'Invalid amount'
        with self.assertRaises(TransactionError) as err:
            self.transaction.update(self.wallet, self.recipient, amount)
            self.assertIn(err_message, err.message)
Beispiel #25
0
 def _generate_transaction(self):
     output = {}
     output[self.wallet.address] = MINING_REWARD
     input = MINING_REWARD_INPUT
     id = uuid.uuid4().int
     return Transaction.create(uuid=id, input=input, output=output)
Beispiel #26
0
 def test_transaction_generate_uuid(self):
     id = Transaction.generate_uuid()
     self.assertTrue(re.match(r'^[0-9]{,39}$', str(id)))
     self.assertIsInstance(uuid.UUID(int=id), uuid.UUID)