Ejemplo n.º 1
0
 def non_redundant_transaction(transaction, chain):
     """
     Check to make sure a transaction isn't already on the chain.
     :param transaction: a pending transaction.
     :param chain: a blockchain to search for the transaction.
     :return: True if not redundant, else false.
     """
     transaction_time = transaction['timestamp']
     sender = transaction['sender']
     recipient = transaction['recipient']
     transaction_seen = False
     for block in chain:
         if block['transactions']:
             for other_transaction in block['transactions']:
                 other_time = other_transaction['timestamp']
                 other_sender = other_transaction['sender']
                 other_recipient = other_transaction['recipient']
                 if transaction_time == other_time and sender == other_sender and recipient == other_recipient:
                     # If we've already seen this transaction, it is redundant. If not, mark it as seen.
                     if transaction_seen:
                         # Transaction is redundant.
                         log("TRANSACTION IS REDUNDANT")
                         return False
                     transaction_seen = True
     return True
Ejemplo n.º 2
0
 def new_block(self, proof, previous_hash):
     """
     Create a new block in the blockchain.
     Make sure not to add transaction to a block that already exist on the blockchain.
     Make sure that a would be transferer has sufficient balance to make a transfer.
     Check that the key used to request each transaction is correct.
     :param proof: The proof given by the proof of work algorithm
     :param previous_hash: Hash of previous block
     :return: A new block
     """
     valid_transactions = []
     for transaction in self.current_transactions:
         # Ensure proper vote signature and sufficient balance.
         if self.valid_transaction(
                 transaction,
                 self.chain) and self.valid_balance(transaction):
             self.update_wallets(transaction)
             valid_transactions.append(transaction)
             log("TRANSACTION ADDED TO NEW BLOCK.")
     block = {
         'index': len(self.chain),
         'timestamp': time(),
         'transactions': valid_transactions,
         'proof': proof,
         'previous_hash': previous_hash or self.hash(self.chain[-1])
     }
     # Reset the current list of transactions
     self.current_transactions = []
     self.chain.append(block)
     log("NEW BLOCK ADDED TO CHAIN.")
     return block
Ejemplo n.º 3
0
    def valid_transaction(self, transaction, chain):
        """
        Checks the validity of a requested transaction by:
        Ensuring transfer amount is positive.
        Checking that the key used to request transaction is correct.
        :return: True if valid, else false
        """
        # Ensure that transaction is not redundant:
        if not self.non_redundant_transaction(transaction, chain):
            return False
        # Ensure transfer amount is positive.
        amount = transaction['amount']
        if amount < 0:
            return False
        sender = transaction['sender']
        if sender == "0":
            # Original vote producer node.
            return True

        # If not an original vote producer vote, then the vote is being
        # transferred, e.g. being cast.
        # Ensure that the vote number is the correct one for this vote:
        vote_number = transaction['vote_number']
        if len(chain) < vote_number:
            return False
        target_node_transactions = chain[vote_number]['transactions']
        if len(target_node_transactions) != 1:
            # The initial vote nodes only have one transaction per block.
            return False
        referenced_voter = target_node_transactions[0]['recipient']
        if sender != referenced_voter:
            return False

        # We now know that someone is trying to cast a vote that is indeed available
        # to be cast. Now we need to verify the signature that the voter provided in
        # order to ascertain that the transaction is actually valid. The signature is
        # the phrase "NO COLLUSION" encrypted with a private RSA key that corresponds
        # to the public key in the 'sender' field.
        voter_private_key = cryptfuncs.import_key(transaction['signature'])
        # A voters signature is the private key that is assigned to their vote.
        # Once used, it is no longer usable. Thus, it doesn't matter that users
        # are publicizing their private key, something one wouldn't want to do
        # in most contexts.
        voter_public_key = cryptfuncs.import_key(sender)

        verification_message = "NO COLLUSION"
        signature = cryptfuncs.sign(verification_message, voter_private_key)
        verification = cryptfuncs.verify(verification_message, signature,
                                         voter_public_key)
        log("{} TRANSACTION".format("GOOD" if verification else "BAD"))
        if not verification:
            log("THE FOLLOWING TRANSACTION WAS NOT VALID: {}".format(
                transaction))
        return verification
Ejemplo n.º 4
0
 def chain_transactions_valid(self, chain):
     """
     Checks the validity of a the transactions in a chain.
     Make sure transaction is not redundant in its chain.
     Make sure transaction key is valid.
     :param chain: The chain to be checked.
     :return: True if valid, else false
     """
     log("CHECKING VALIDITY OF TRANSACTIONS.")
     for block in chain:
         for transaction in block['transactions']:
             if not self.valid_transaction(transaction, chain):
                 return False
     return True
Ejemplo n.º 5
0
 def resolve_conflicts(self):
     """
     Resolves conflicts by replacing our chain with the longest one in the network.
     :return: True if chain was replaced, False if not.
     """
     log("RESOLVING CONFCLICTS.")
     neighbours = list(self.nodes)
     new_chain = None
     new_wallets = self.wallets
     new_chain_value = self.total_value
     # Only looking for longer chains:
     max_length = len(self.chain)
     # Grab and verify the chains from all the nodes in the network
     for node in neighbours:
         response = None
         for i in range(5):
             try:
                 response = requests.get(f'http://{node}/chain/')
                 if response:
                     break
             except:
                 if i == 4:
                     # Remove unresponsive nodes.
                     self.nodes.discard(node)
                 continue
         if response:
             if response.status_code == 200:
                 length = response.json()['length']
                 chain = response.json()['chain']
                 # Check if the length is longer and the chain is valid
                 log("CHECKING CHAIN.")
                 if length > max_length and self.valid_chain(chain):
                     log("CHAIN IS VALID. CHECKING BALANCES.")
                     valid_wallets, new_wallets, new_chain_value = self.valid_wallets(
                         chain)
                     # If all blocks have correct hashes, and all blocks have valid
                     # transactions, and the new chain leads to valid wallets.
                     if valid_wallets:
                         max_length = length
                         new_chain = chain
     # Replace this node's chain if a new, valid, longer chain is discovered:
     if new_chain:
         log("REPLACING THIS NODE'S CHAIN WITH NEW ONE.")
         self.chain = new_chain
         self.wallets = new_wallets
         self.total_value = new_chain_value
         return True
     log("KEEPING CURRENT CHAIN.")
     return False
Ejemplo n.º 6
0
 def valid_chain(self, chain):
     """
     Determine if a given blockchain is valid.
     :param chain: A blockchain
     :return: True if valid, False if not
     """
     last_block = chain[0]
     for current_index in range(1, len(chain)):
         block = chain[current_index]
         # Check that the hash of the block is correct
         last_block_hash = self.hash(last_block)
         if block['previous_hash'] != last_block_hash:
             return False
         # Check that the proof of work is correct
         if not self.valid_proof(last_block['proof'], block['proof'],
                                 last_block_hash):
             return False
         last_block = block
     # Return true only if all transactions on the chain are valid:
     log("CHAIN HASHES ARE CORRECT. CHECKING FOR VALID TRANSACTIONS.")
     return self.chain_transactions_valid(chain)
Ejemplo n.º 7
0
def cache(func, *args):
    """
    Put expensive calculatinos in cache on file that can be retrieved even after
    program has terminated
    @param:
            obj - object to be cached
    value - name of object
    @return:
            return value
    args - optional arguments given to the object
    # >>> a = binary_search(1, 12, 1)
    # >>> cache_put(a, [1, 12, 1])
    >>> cache(binary_search, 1, 12, 1) #doctest: +NORMALIZE_WHITESPACE
    3
    >>> cache(binary_search, 2, 12, 1) #doctest: +SKIP
    3
    """
    global CACHE_HIT
    _initialize()
    # Hash is function name + values
    cache_index = hash(str(func.func_name) + str(list(args)))
    cache_name = os.path.join(SIMPLECACHE_DIR, str(cache_index))

    # If we find file, load it
    if os.path.exists(cache_name):
        log("cache hit")
        with open(cache_name, "rb") as fh:
            r = pickle.load(fh)
            CACHE_HIT = True
            return r
    # Object doesn't exist, put it in cache
    else:
        r = func(*args)
        with open(cache_name, "wb") as fh:
            log("cache miss")
            pickle.dump(r, fh, pickle.HIGHEST_PROTOCOL)
        CACHE_HIT = False
        return r
Ejemplo n.º 8
0
def cache(func, *args):
    """
    Put expensive calculatinos in cache on file that can be retrieved even after
    program has terminated
    @param:
            obj - object to be cached
    value - name of object
    @return:
            return value
    args - optional arguments given to the object
    # >>> a = binary_search(1, 12, 1)
    # >>> cache_put(a, [1, 12, 1])
    >>> cache(binary_search, 1, 12, 1) #doctest: +NORMALIZE_WHITESPACE
    3
    >>> cache(binary_search, 2, 12, 1) #doctest: +SKIP
    3
    """
    global CACHE_HIT
    _initialize()
    # Hash is function name + values
    cache_index = hash(str(func.func_name) + str(list(args)))
    cache_name = os.path.join(SIMPLECACHE_DIR, str(cache_index))

    # If we find file, load it
    if (os.path.exists(cache_name)):
        log('cache hit')
        with open(cache_name, "rb") as fh:
            r = pickle.load(fh)
            CACHE_HIT = True
            return r
    # Object doesn't exist, put it in cache
    else:
        r = func(*args)
        with open(cache_name, "wb") as fh:
            log('cache miss')
            pickle.dump(r, fh, pickle.HIGHEST_PROTOCOL)
        CACHE_HIT = False
        return r
Ejemplo n.º 9
0
 def valid_balance(self, transaction):
     """
     Checks to see if a sender has sufficient balance
     (a vote left to cast) in order to make a transaction.
     :param transaction: A transaction with a sender, reciever, and amount.
     :return: True if balance is sufficient, else false.
     """
     log("CHECKING BALANCE.")
     amount = transaction['amount']
     sender = transaction['sender']
     if sender == "0" and not self.lock:
         # "0" sender, is the original source, and is allowed
         return True
     if sender in self.wallets:
         log("SENDER FOUND IN WALLET.")
         if self.wallets[sender] >= amount:
             log("BALANCE SUFFICIENT.")
             return True
     log("{} DOES NOT HAVE A SUFFICIENT BALANCE OR DOES NOT EXIST.".format(
         sender))
     return False