Ejemplo n.º 1
0
 def request_transaction(self, node, port, tx_hash):
     url = self.TRANSACTIONS_URL.format(node, port, tx_hash)
     try:
         response = requests.get(url)
         if response.status_code == 200:
             tx_dict = response.json()
             transaction = Transaction(
                 tx_dict['source'],
                 tx_dict['destination'],
                 tx_dict['amount'],
                 tx_dict['fee'],
                 tx_dict['prev_hash'],
                 tx_dict['tx_type'],
                 tx_dict['timestamp'],
                 tx_dict['tx_hash'],
                 tx_dict['asset'],
                 tx_dict['data'],
                 tx_dict['signature']
             )
             if transaction.tx_hash != tx_dict['tx_hash']:
                 logger.warn("Invalid transaction hash: {} should be {}.  Transaction ignored."
                             .format(tx_dict['tx_hash'], transaction.tx_hash))
                 return None
             return transaction
     except requests.exceptions.RequestException as re:
         logger.warn("Request Exception with host: {}".format(node))
         self.peers.record_downtime(node)
     return None
Ejemplo n.º 2
0
 def __synchronize(self, node):
     # synchronize with sender
     repeat_sync = True
     while repeat_sync is True:
         current_height = self.blockchain.get_height()
         peer_height = self.api_client.request_height(node)
         if peer_height is not None and peer_height > current_height:
             # 100 blocks of overlap should be sufficient to find a common block lest we are on a forked branch
             start_height = current_height - 100 if current_height > 100 else 1
             if current_height < peer_height - 500:
                 # we are way behind.
                 end_height = start_height + 500
             else:
                 end_height = peer_height
                 repeat_sync = False
             peer_blocks_inv = self.api_client.audit(
                 node, start_height, end_height)
             last_common_block = self.__find_last_common_block(
                 peer_blocks_inv)
             if last_common_block is None:
                 logger.warn(
                     "Completely out of sync with peer at {}".format(node))
                 break
             block_header, branch, height = last_common_block
             # construct list of missing block hashes to request from the peer
             hashes_to_query = peer_blocks_inv[peer_blocks_inv.
                                               index(block_header.hash) +
                                               1:]
             for block_hash in hashes_to_query:
                 block_header = self.api_client.request_block_header(
                     node, self.FULL_NODE_PORT, block_hash=block_hash)
                 self.__process_block_header(block_header, node)
         else:
             repeat_sync = False
Ejemplo n.º 3
0
 def validate_block(self, block, merkle_root):
     if block.block_header.merkle_root != merkle_root:
         logger.warn("invalid merkle root")
         return False
     if not self.check_block_reward(block):
         logger.warn("Invalid block reward")
         return False
     return True
Ejemplo n.º 4
0
 def request_blocks_inv(self, node, port, start_height, stop_height):
     # Used when a synchronization between peers is needed
     url = self.BLOCKS_INV_URL.format(node, port, start_height, stop_height)
     try:
         response = requests.get(url)
         if response.status_code == 200:
             block_dict = response.json()
             return block_dict['block_hashes']
     except requests.exceptions.RequestException as re:
         logger.warn("Request Exception with host: {}".format(node))
         self.peers.record_downtime(node)
     return None
Ejemplo n.º 5
0
 def audit(self, node, start_height, end_height):
     # Audit node's blocks_inv and sync if necessary
     url = self.BLOCKS_INV_URL.format(node, self.FULL_NODE_PORT, start_height, end_height)
     try:
         response = requests.get(url)
         if response.status_code == 200:
             tx_dict = response.json()
             return tx_dict.get('blocks_inv')
     except requests.exceptions.RequestException as re:
         logger.warn("Request Exception with host: {}".format(node))
         self.peers.record_downtime(node)
     return None
Ejemplo n.º 6
0
 def request_transactions_inv(self, node, port, block_hash):
     # Request a list of transaction hashes that belong to a block hash. Used when recreating a block from a
     # block header
     url = self.TRANSACTIONS_INV_URL.format(node, port, block_hash)
     try:
         response = requests.get(url)
         if response.status_code == 200:
             tx_dict = response.json()
             return tx_dict['tx_hashes']
     except requests.exceptions.RequestException as re:
         logger.warn("Request Exception with host: {}".format(node))
         self.peers.record_downtime(node)
     return None
Ejemplo n.º 7
0
 def push_synchronize(self, node, blocks_inv, current_height, host):
     # Push local blocks_inv to remote node to initiate a sync
     data = {
         "host": host,
         "type": MessageType.SYNCHRONIZE.value,
         "data": {"height": current_height, "blocks_inv": blocks_inv}
     }
     logger.debug("sending sync request to peer at: {}".format(node))
     url = self.INBOX_URL.format(node, self.FULL_NODE_PORT)
     try:
         response = requests.post(url, json=data)
     except requests.exceptions.RequestException as re:
         logger.warn("Request Exception with host: {}".format(node))
         self.peers.record_downtime(node)
     return
Ejemplo n.º 8
0
 def broadcast_block_inv(self, block_hashes, host):
     # Used for (re)broadcasting a new block that was received and added
     data = {
         "host": host,
         "type": MessageType.BLOCK_INV.value,
         "data": block_hashes
     }
     logger.debug("broadcasting block inv: {}".format(data))
     for node in self.peers.get_all_peers():
         url = self.INBOX_URL.format(node, self.FULL_NODE_PORT)
         try:
             response = requests.post(url, json=data)
         except requests.exceptions.RequestException as re:
             logger.warn("Request Exception with host: {}".format(node))
             self.peers.record_downtime(node)
     return
Ejemplo n.º 9
0
 def broadcast_unconfirmed_transaction_inv(self, tx_hashes, host):
     # Used for (re)broadcasting a new transaction that was received and added
     data = {
         "host": host,
         "type": MessageType.UNCONFIRMED_TRANSACTION_INV.value,
         "data": tx_hashes
     }
     logger.debug("broadcasting transaction inv: {}".format(data))
     for node in self.peers.get_all_peers():
         url = self.INBOX_URL.format(node, self.FULL_NODE_PORT)
         try:
             response = requests.post(url, json=data)
         except requests.exceptions.RequestException as re:
             logger.warn("Request Exception with host: {}".format(node))
             self.peers.record_downtime(node)
     return
Ejemplo n.º 10
0
 def broadcast_block_header(self, block_header, host):
     # Used only when broadcasting a block header that originated (mined) locally
     data = {
         "host": host,
         "type": MessageType.BLOCK_HEADER.value,
         "data": block_header.to_json()
     }
     logger.debug("broadcasting block header: {}".format(data))
     for node in self.peers.get_all_peers():
         url = self.INBOX_URL.format(node, self.FULL_NODE_PORT)
         try:
             response = requests.post(url, json=data)
         except requests.exceptions.RequestException as re:
             logger.warn("Request Exception with host: {}".format(node))
             self.peers.record_downtime(node)
     return
Ejemplo n.º 11
0
 def request_block_header(self, node, port, block_hash=None, height=None):
     if block_hash is not None:
         url = self.BLOCKS_URL.format(node, port, "hash", block_hash)
     elif height is not None:
         url = self.BLOCKS_URL.format(node, port, "height", height)
     else:
         url = self.BLOCKS_URL.format(node, port, "height", "latest")
     try:
         response = requests.get(url)
         if response.status_code == 200:
             block_dict = response.json()
             block_header = BlockHeader(block_dict['previous_hash'],
                                        block_dict['merkle_root'],
                                        block_dict['timestamp'],
                                        block_dict['nonce'],
                                        block_dict['version'])
             return block_header
     except requests.exceptions.RequestException as re:
         logger.warn("Request Exception with host: {}".format(node))
         self.peers.record_downtime(node)
     return None
Ejemplo n.º 12
0
    def validate_transaction(self, transaction):
        """
        Validate a single transaction.  Check for double-spend, invalid signature, and insufficient funds

        :param transaction:
        :return: boolean
        :rtype: boolean
        """
        if self.blockchain.find_duplicate_transactions(transaction.tx_hash):
            logger.warn(
                'Transaction not valid.  Double-spend prevented: {}'.format(
                    transaction.tx_hash))
            return False
        if not transaction.verify():
            logger.warn(
                'Transaction not valid.  Invalid transaction signature: {}'.
                format(transaction.tx_hash))
            return False
        balance = self.blockchain.get_balance(transaction.source)
        if transaction.amount + transaction.fee > balance:
            logger.warn(
                'Transaction not valid.  Insufficient funds: {}'.format(
                    transaction.tx_hash))
            return False
        return True
Ejemplo n.º 13
0
    def validate_block_transactions_inv(self, transactions_inv):
        """
        Checks a list of transaction hashes, checks for double-spends and/or entries in the mempool
        Returns a list of unknown transaction hashes

        :param transactions_inv:
        :return: block_transactions, missing_transactions_inv
        :rtype: tuple(list, list)
        """
        missing_transactions_inv = []
        block_transactions = []
        for tx_hash in transactions_inv:
            if self.blockchain.find_duplicate_transactions(tx_hash):
                logger.warn(
                    'Transaction not valid.  Double-spend prevented: {}'.
                    format(tx_hash))
                return False
            transaction = self.mempool.get_unconfirmed_transaction(tx_hash)
            if transaction is None:
                missing_transactions_inv.append(tx_hash)
            else:
                block_transactions.append(transaction)
        return block_transactions, missing_transactions_inv
Ejemplo n.º 14
0
 def validate_block_header(self, block_header, transactions_inv):
     if self.blockchain.get_block_header_by_hash(block_header.hash):
         logger.warn('Block Header already exists')
         return False
     if block_header.version != config['network']['version']:
         logger.warn('Incompatible version')
         return False
     if block_header.merkle_root != self.calculate_merkle_root(
             transactions_inv):
         logger.warn('Invalid merkle root')
         return False
     previous_block = self.blockchain.get_block_header_by_hash(
         block_header.previous_hash)
     if previous_block is None:
         return None
     previous_block_header, previous_block_branch, previous_block_height = previous_block
     if self.blockchain.calculate_hash_difficulty(
             previous_block_height + 1) > block_header.hash_difficulty:
         logger.warn('Invalid hash difficulty')
         return False
     return previous_block_height + 1
Ejemplo n.º 15
0
 def check_block_reward(self, block):
     reward_amount = self.blockchain.get_reward(block.height)
     for transaction in block.transactions[1:]:
         if TransactionType(
                 transaction.tx_type) == TransactionType.COINBASE:
             logger.warn("Block not valid.  Multiple coinbases detected")
             return False
         reward_amount += transaction.fee
     # first transaction is coinbase
     reward_transaction = block.transactions[0]
     if TransactionType(
             reward_transaction.tx_type) != TransactionType.COINBASE:
         logger.warn("Block not valid.  Missing coinbase")
         return False
     if reward_transaction.amount != reward_amount:
         logger.warn("Invalid block reward {} should be {}".format(
             reward_transaction.amount, reward_amount))
         return False
     if reward_transaction.source != "0":
         logger.warn("Invalid Coinbase source")
         return False
     return True
Ejemplo n.º 16
0
    def check_peers_full(self, host, known_peers):
        if self.peers.get_peers_count() < self.MIN_PEERS:
            host_data = {"host": host, "network": config['network']}

            for peer in known_peers:
                if self.peers.get_peers_count() >= self.MAX_PEERS:
                    break
                if peer == host:
                    continue

                status_url = self.STATUS_URL.format(peer, self.FULL_NODE_PORT)
                connect_url = self.CONNECT_URL.format(peer,
                                                      self.FULL_NODE_PORT)
                try:
                    response = requests.get(status_url)
                    if response.status_code != 200:  # Downtime or error
                        if self.peers.get_peer(peer):
                            self.peers.record_downtime(peer)
                            logger.warn("Downtime recorded for node %s", peer)
                        continue
                    if response.json(
                    ) != config['network']:  # Incompatible network
                        if self.peers.get_peer(peer):
                            self.peers.remove_peer(peer)
                        logger.warn("Incompatible network with node %s", peer)
                        continue
                    if self.peers.get_peer(peer) is None:
                        response = requests.post(connect_url, json=host_data)
                        if response.status_code == 202 and response.json().get(
                                "success") is True:
                            self.peers.add_peer(peer)
                except requests.exceptions.RequestException as re:
                    logger.warn(
                        "Request exception while attempting to reach %s", peer)
                    if self.peers.get_peer(peer):
                        self.peers.record_downtime(peer)
        return
Ejemplo n.º 17
0
def client():
    helptext = '''
        Available commands:
        ===================
        balance <public key (optional)>
        send <destination> <amount> <fee>
        publickey
        privatekey
        history <public key (optional)>
        quit or exit
    '''
    peers = Peers()
    api_client = ApiClient(peers)
    encrypted = config['user']['encrypted_private_key']
    if encrypted is None:
        print(
            "\n\nNo private key provided. A new wallet will be generated for you...\n\n"
        )
        wallet = Client(peers, api_client)
    else:
        passphrase = getpass("Enter passphrase: ")
        encrypted = codecs.decode(encrypted, 'hex')
        nonce = encrypted[0:16]
        tag = encrypted[16:32]
        ciphertext = encrypted[32:]
        hashedpass = hashlib.sha256(passphrase.encode('utf-8')).digest()
        cipher = AES.new(hashedpass, AES.MODE_EAX, nonce)
        try:
            private_key = cipher.decrypt_and_verify(ciphertext, tag)
            wallet = Client(peers, api_client, private_key)
        except ValueError as ve:
            logger.warn('Invalid passphrase')
            print("\n\nInvalid passphrase\n\n")
            sys.exit(1)

    while True:
        cmd = input("{} ({}) wallet > ".format(
            config['network']['name'], config['network']['ticker_symbol']))
        cmd_split = cmd.split()
        try:
            if cmd_split[0] == "balance":
                if len(cmd_split) == 2:
                    print(wallet.get_balance(cmd_split[1]))
                else:
                    print(wallet.get_balance())
            elif cmd_split[0] == "send":
                if len(cmd_split) == 4:
                    print(
                        wallet.create_transaction(cmd_split[1],
                                                  float(cmd_split[2]),
                                                  float(cmd_split[3])))
                else:
                    print("\nRequires destination, amount, fee\n")
            elif cmd_split[0] == "publickey":
                print(wallet.get_public_key())
            elif cmd_split[0] == "privatekey":
                print(wallet.get_private_key())
            elif cmd_split[0] == "history":
                if len(cmd_split) == 2:
                    print(wallet.get_transaction_history(cmd_split[1]))
                else:
                    print(wallet.get_transaction_history())
            elif cmd_split[0] in ("quit", "exit"):
                sys.exit(0)
            else:  # help
                print(helptext)
        except IndexError:
            pass
Ejemplo n.º 18
0
 def worker(self):
     while True:
         msg = Queue.dequeue()
         sender = msg.get('host', '')
         msg_type = MessageType(msg.get('type'))
         data = msg.get('data')
         if msg_type == MessageType.BLOCK_HEADER:
             block_header = BlockHeader.from_dict(json.loads(data))
             if sender == self.HOST:
                 self.api_client.broadcast_block_inv([block_header.hash],
                                                     self.HOST)
             else:
                 self.__process_block_header(block_header, sender)
             continue
         elif msg_type == MessageType.UNCONFIRMED_TRANSACTION:
             unconfirmed_transaction = Transaction.from_dict(data)
             if sender == self.HOST:
                 # transaction already validated before being enqueued
                 valid = True
             else:
                 valid = self.validator.validate_transaction(
                     unconfirmed_transaction)
             if valid:
                 self.api_client.broadcast_unconfirmed_transaction_inv(
                     [unconfirmed_transaction.tx_hash], self.HOST)
             continue
         elif msg_type == MessageType.BLOCK_INV:
             missing_block_headers = []
             for block_hash in data:
                 # aggregate unknown block header hashes
                 block_header = self.blockchain.get_block_header_by_hash(
                     block_hash)
                 if block_header is None:
                     missing_block_headers.append(block_hash)
             for block_hash in missing_block_headers:
                 # We don't have these blocks in our database.  Fetch them from the sender
                 block_header = self.api_client.request_block_header(
                     sender, self.FULL_NODE_PORT, block_hash=block_hash)
                 self.__process_block_header(block_header, sender)
             continue
         elif msg_type == MessageType.UNCONFIRMED_TRANSACTION_INV:
             missing_transactions = []
             new_unconfirmed_transactions = []
             for tx_hash in data:
                 # skip known unconfirmed transactions
                 transaction = self.blockchain.get_transaction_by_hash(
                     tx_hash)
                 if transaction:
                     continue
                 unconfirmed_transaction = self.mempool.get_unconfirmed_transaction(
                     tx_hash)
                 if unconfirmed_transaction:
                     continue
                 missing_transactions.append(tx_hash)
             for tx_hash in missing_transactions:
                 # retrieve unknown unconfirmed transactions
                 transaction = self.api_client.request_transaction(
                     sender, self.FULL_NODE_PORT, tx_hash)
                 valid = self.validator.validate_transaction(transaction)
                 if valid:
                     # validate and store retrieved unconfirmed transactions
                     self.mempool.push_unconfirmed_transaction(transaction)
                     new_unconfirmed_transactions.append(tx_hash)
             if len(new_unconfirmed_transactions):
                 # broadcast new unconfirmed transactions
                 self.api_client.broadcast_unconfirmed_transaction_inv(
                     new_unconfirmed_transactions)
             continue
         else:
             logger.warn("Encountered unknown message type %s from %s",
                         msg_type, sender)
             pass