Beispiel #1
0
    def load_keys(self, private_key):
        """Loads the wallet based on the private key."""

        # prepare the private_key input to be transformed to the public_key
        try:
            hex_to_pem = binascii.unhexlify(''.join(private_key))
            pem_key = b'%s' % hex_to_pem
            kep_priv = RSA.importKey(pem_key)
            candidate_key = kep_priv.publickey()
            query_key = binascii.hexlify(
                candidate_key.exportKey(format='DER')).decode('ascii')
        except ValueError as e:
            return e

        # Pass the candidate key for the
        # SQL query and search database for the public_key
        session = Session()
        try:
            public_key = session.query(Wallet.public_key)\
                .filter(text('public_key == :query_key')).params(query_key=query_key)\
                .one()
        except NoResultFound as e:
            return e
        session.close()

        self.public_key = ''.join(public_key)
        self.private_key = private_key
        return True
Beispiel #2
0
    def get_all_transactions(self):
        """Returns all transactions of the local blockchain"""
        session = Session()
        all_transactions = session.query(Transaction).all()
        session.close()

        sendable_tx = self.make_sendable_list(all_transactions)
        return sendable_tx
Beispiel #3
0
    def get_balance(self, sender=None):
        """Calculate and return the balance for a participant.
        """
        if sender is None:
            if self.public_key is None:
                return None
            participant = self.public_key
        else:
            participant = sender
        # Fetch a list of all sent coin amounts for the given person (empty
        # lists are returned if the person was NOT the sender)
        # This fetches sent amounts of transactions that were already included
        # in blocks of the blockchain
        session = Session()
        tx_sender = session.query(Transaction.amount)\
            .filter(text('sender == :participant'))\
            .params(participant=str(participant)).all()
        session.close()

        # Fetch a list of all sent coin amounts for the given person (empty
        # lists are returned if the person was NOT the sender)
        # This fetches sent amounts of open transactions (to avoid double
        # spending)
        amount_sent = reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt)
        if len(tx_amt) > 0 else tx_sum + 0, tx_sender, 0)
        # This fetches received coin amounts of transactions that were already
        # included in blocks of the blockchain
        # We ignore open transactions here because you shouldn't be able to
        # spend coins before the transaction was confirmed + included in a
        # block
        session = Session()
        tx_recipient = session.query(Transaction.amount)\
            .filter(text('recipient == :participant AND mined == :mined'))\
            .params(participant=str(participant), mined=1).all()
        session.close()

        amount_received = reduce(
            lambda tx_sum, tx_amt: tx_sum + sum(tx_amt)
            if len(tx_amt) > 0 else tx_sum + 0,
            tx_recipient,
            0
        )
        # Return the total balance
        return amount_received - amount_sent
Beispiel #4
0
    def remove_peer_node(self, node):
        """Removes a node from the peer node set.

        Arguments:
            :node: The node URL which should be removed.
        """
        session = Session()
        obj = session.query(Node).filter(text("id == :node_id"))\
            .params(node_id=node).one()
        session.delete(obj)
        try:
            session.commit()
        except NoResultFound:
            return False
        session.close()
        self.load_data()
        return True
Beispiel #5
0
 def save_keys(self):
     """Saves the keys to a file (wallet.txt)."""
     if self.public_key is not None and self.private_key is not None:
         try:
             with open('wallet-{}.txt'.format(self.node_id), mode='w') as f:
                 f.write(self.private_key)
             session = Session()
             wallet = Wallet(node_id=self.node_id,
                             public_key=self.public_key)
             session.add(wallet)
             session.commit()
             session.close()
             return True
         except (IOError, IndexError):
             print('Saving wallet failed...')
             return False
Beispiel #6
0
    def add_peer_node(self, node):
        """Adds a new node to the peer node set.

        Arguments:
            :node: The node URL which should be added.
        """
        session = Session()
        new_peer_node = Node(node)
        session.add(new_peer_node)
        try:
            session.commit()
        except IntegrityError:
            return False
        session.close()
        self.load_data()
        return True
Beispiel #7
0
    def add_transaction(self,
                        recipient,
                        sender,
                        signature,
                        amount=1.0,
                        time=0,
                        is_receiving=False):
        """ Append a new value as well as the last blockchain value to the blockchain.

        Arguments:
            :sender: The sender of the coins.
            :recipient: The recipient of the coins.
            :signature: The signature of the transaction.
            :amount: The amount of coins sent with the transaction
            :time: The time when the transaction was made (generated with time())
            (default = 1.0)
        """
        session = Session()
        transaction = Transaction(sender, recipient, signature, amount, timed=time)
        session.add(transaction)
        if Verification.verify_transaction(transaction, self.get_balance):
            session.commit()
            session.close()
            self.load_data()

            if not is_receiving:
                for node in self.__peer_nodes:
                    url = 'http://{}/broadcast-transaction'.format(node['id'])
                    try:
                        response = requests.post(url,
                                                 json={
                                                     'sender': sender,
                                                     'recipient': recipient,
                                                     'amount': amount,
                                                     'signature': signature,
                                                     'time': time
                                                 })
                        if (response.status_code == 400 or
                                response.status_code == 500):
                            print('Transaction declined, needs resolving')
                            return False
                    except requests.exceptions.ConnectionError:
                        continue
            return True
        return False
Beispiel #8
0
    def load_data(self):
        """Initialize blockchain + open transactions data from a file."""

        session = Session()
        blockchain_objects = session.query(Block).all()
        blockchain = self.make_sendable_list(blockchain_objects)

        all_mined_transactions_objects = session.query(Transaction)\
            .filter(Transaction.mined == 1).all()
        all_mined_transactions = self.make_sendable_list(all_mined_transactions_objects)

        session.close()
        self.chain = blockchain
        self.mined_transactions = all_mined_transactions

        if len(blockchain) == 0:
            # Our starting block for the blockchain
            session = Session()
            genesis_block = Block(0, 'GENESIS', 'GENESIS', 100, -1)
            session.add(genesis_block)
            session.commit()
            self.chain = session.query(Block).all()
            session.close()

        session = Session()
        open_transactions_objects = session.query(Transaction)\
            .filter(Transaction.mined == 0).all()
        open_transactions = self.make_sendable_list(open_transactions_objects)
        self.__open_transactions = open_transactions

        peer_nodes_objects = session.query(Node).all()
        peer_nodes = self.make_sendable_list(peer_nodes_objects)
        self.__peer_nodes = peer_nodes
        session.close()
Beispiel #9
0
    def resolve(self):
        """Checks all peer nodes' blockchains and replaces the local one with
        longer valid ones."""
        # Initialize the winner chain with the local chain
        winner_chain = self.chain
        winning_node = self.node_id
        replace = False
        for node in self.__peer_nodes:
            url = 'http://{}/chain'.format(node['id'])
            try:
                # Send a request and store the response
                response = requests.get(url)
                # Retrieve the JSON data as a dictionary
                peer_node_data = response.json()
                node_chain = peer_node_data['chain']
                # Convert the dictionary list to a list of block AND
                # transaction objects

                node_chain = [
                    Block(
                        block['index'],
                        block['previous_hash'],
                        block['hash_of_txs'],
                        block['proof'],
                        block['timestamp']) for block in node_chain
                ]
                node_chain_length = len(node_chain)
                local_chain_length = len(winner_chain)
                # Store the received chain as the current winner chain if it's
                # longer AND valid
                if (node_chain_length > local_chain_length and
                        Verification.verify_chain(node_chain)):
                    winner_chain = node_chain
                    winning_node = node['id']
                    replace = True
            except requests.exceptions.ConnectionError:
                continue
        self.resolve_conflicts = False
        # Replace the local chain with the winner chain
        if self.chain is not winner_chain and replace:
            # get transactions from winning chain to replace the local ones
            url = 'http://{}/gettransactions'.format(winning_node)
            response = requests.get(url)
            transactions_object = response.json()
            transactions = transactions_object['transactions']
            session = Session()
            # delete local content of databases
            session.query(Transaction).delete()
            session.query(Block).delete()
            try:
                session.commit()
            except NoResultFound as e:
                print(e)

            new_transactions = [
                Transaction(
                    tx['sender'],
                    tx['recipient'],
                    tx['signature'],
                    tx['amount'],
                    tx['mined'],
                    tx['block'],
                    tx['time']) for tx in transactions
            ]

            session.add_all(new_transactions)
            session.add_all(winner_chain)
            session.commit()

        self.load_data()
        return replace
Beispiel #10
0
    def add_block(self, block, list_of_transactions):
        """Add a block which was received via broadcasting to the local
        blockchain."""

        # Validate the proof of work of the block and store the result (True
        # or False) in a variable
        proof_is_valid = Verification.valid_proof(
            block['hash_of_txs'], block['previous_hash'], block['proof'])

        # Check if previous_hash stored in the block is equal to the local
        # blockchain's last block's hash and store the result in a block
        last_block = self.__chain[-1]
        index_of_block = block['index']
        hashes_match = hash_block(last_block) == block['previous_hash']
        if not proof_is_valid or not hashes_match:
            return False

        # Create a Block object
        session = Session()
        converted_block = Block(
            index_of_block,
            block['previous_hash'],
            block['hash_of_txs'],
            block['proof'],
            block['timestamp'])
        session.add(converted_block)

        # create a Transaction object of the given mining reward transactions
        # since this Transaction is not broadcasted
        reward_transaction = list_of_transactions[-1]
        reward_tx = Transaction(
            reward_transaction['sender'],
            reward_transaction['recipient'],
            reward_transaction['signature'],
            reward_transaction['amount'],
            1, index_of_block,
            reward_transaction['time'])
        session.add(reward_tx)

        session.commit()
        session.close()
        self.load_data()

        # Check which open transactions were included in the received block
        # and update the mined and block columns (except for the last transaction
        # in the list, that is the mining reward)
        mined_transactions = []
        session = Session()
        for itx in list_of_transactions[:-1]:
            try:
                for opentx in session.query(Transaction).filter(text("signature = :sign"))\
                        .params(sign=itx['signature']).all():
                            mined_transactions.append(opentx)
            except NoResultFound:
                continue

        for tx in mined_transactions:
            tx.block = index_of_block
            tx.mined = 1

        session.commit()
        session.close()

        self.load_data()
        return True
Beispiel #11
0
    def mine_block(self):
        """Create a new block and add open transactions to it."""
        # Fetch the currently last block of the blockchain
        if self.public_key is None:
            return None
        last_block = self.__chain[-1]
        # Hash the last block (=> to be able to compare it to the stored hash
        # value)
        block_index = int(len(self.__chain))
        print(block_index)
        self.load_data()
        reward_transaction = Transaction(
            'MINING', str(self.public_key),
            'REWARD FOR MINING BLOCK {}'.format(block_index),
            MINING_REWARD, 1, block_index, time())
        print("rwd tx: ", reward_transaction.block)
        # Copy transaction instead of manipulating the original
        # open_transactions list
        # This ensures that if for some reason the mining should fail,
        # we don't have the reward transaction stored in the open transactions
        copied_transactions = self.__open_transactions[:]
        for tx in copied_transactions:
            if not Wallet.verify_transaction(tx):
                return None

        copied_transactions.append(reward_transaction)

        # add and modify the objects in the database
        hashed_transactions = Transaction.to_merkle_tree(copied_transactions)
        hashed_block = hash_block(last_block)
        proof = self.proof_of_work(hashed_transactions)
        session = Session()
        block = Block(block_index, hashed_block,
                      hashed_transactions, proof)
        session.add(block)
        session.add(reward_transaction)
        session.commit()
        open_txs = session.query(Transaction).filter(Transaction.mined == 0).all()

        for tx in open_txs:
            tx.block = block_index
            tx.mined = 1

        session.commit()
        session.close()

        self.load_data()

        session = Session()
        converted_block = session.query(Block)\
            .filter(Block.index == block_index).one()
        mined_transactions = session.query(Transaction).\
            filter(Transaction.block == block_index).all()
        session.close()

        sendable_tx = self.make_sendable_list(mined_transactions)

        dict_block = converted_block.__dict__.copy()
        del dict_block['_sa_instance_state']

        for node in self.__peer_nodes:
            url = 'http://{}/broadcast-block'.format(node['id'])
            try:
                response = requests.post(url, json={'block': dict_block,
                                                    'transactions': sendable_tx})
                if response.status_code == 400 or response.status_code == 500:
                    print('Block declined, needs resolving')
                if response.status_code == 409:
                    self.resolve_conflicts = True
            except requests.exceptions.ConnectionError:
                continue
        return block