예제 #1
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)
예제 #2
0
 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)
예제 #3
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
예제 #4
0
 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)
예제 #5
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()
예제 #6
0
 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))
예제 #7
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)
예제 #8
0
    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
예제 #9
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)
예제 #10
0
    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
예제 #11
0
    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)
예제 #12
0
 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)
예제 #13
0
 def _generate_address(self):
     return Wallet.generate_address()
예제 #14
0
 def wallet(self) -> Wallet:
     if not hasattr(self, '_wallet'):
         self._wallet = Wallet(self.blockchain)
     return self._wallet
예제 #15
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)