def process_incoming_msg(msg: bytes, in_address: Address, receive_queue: Queue): """ Process messages received from other nodes Args: msg: Received message in_address: Address of the sender receive_queue: Queue for communication with the blockchain """ try: msg_type, msg_data = unpack_msg(msg) except ValueError as err: print_debug_info(f'Received invalid message\n {err}') return PEERS.peer_seen(in_address) print_debug_info('received: ' + msg_type) if msg_type.startswith('N_'): # networking messages if msg_type == 'N_new_peer': PEERS.peer_inferred(msg_data) elif msg_type == 'N_get_peers': get_peers(in_address) elif msg_type == 'N_ping': send_msg('N_pong', '', in_address) elif msg_type == 'N_pong': pass # Only needed for peer update. Ignore! else: # blockchain messages receive_queue.put((msg_type, msg_data, in_address))
def test_debug_info(capsys): """ Test printing of debug info """ utils.set_debug() utils.print_debug_info('TEST') captured = capsys.readouterr() assert captured.out == '### DEBUG ### TEST\n'
def load_chain(self): if os.path.exists('ddos_bc_file.txt') and \ os.stat('ddos_bc_file.txt').st_size != 0 and \ Path('ddos_bc_file.txt').is_file(): print_debug_info('Load existing blockchain from file') with open('ddos_bc_file.txt', 'r') as bc_file: self.chain = serializer.deserialize(bc_file.read()) else: self.chain[DDosHeader(0, 0, 768894480, 0, 0)] = []
def new_block(self, block: Block): # Main chain if self.validate_block(block, self.latest_block()): self.process_block(block) self.chain[block.header] = block.transactions self.send_queue.put(('new_block', block, 'broadcast')) if self.gui_ready: self.gui_queue.put(('new_block', block, 'local')) else: print_debug_info('Block not for main chain') self.check_new_chain(block)
def check_new_chain(self, block): if block.header in self.new_chain: if block.header.root_hash ==\ self.create_merkle_root(block.transactions): # Validate transactions<->header self.new_chain[block.header] = block.transactions for t in block.transactions: try: # Remove processed transactions self.intermediate_transactions.remove(t) except ValueError: pass # Check if new chain is finished if not any(t is None for t in self.new_chain.values()): # Validate transactions old_data = next(iter(self.new_chain.items())) for h, t in self.new_chain.items(): if h == old_data[0]: continue if self.validate_block(Block(h, t), Block(old_data[0], old_data[1]), True): old_data = (h, t) else: print_debug_info( 'Invalid transaction in new chain') self.new_chain.clear() self.intermediate_transactions.clear() return self.send_queue.put( ('new_header', self.nc_latest_header(), 'broadcast')) self.chain = OrderedDict(self.new_chain) self.new_chain.clear() # Remove mining transactions not_processed_transactions = [ t for t in self.intermediate_transactions if t.sender != '0' ] self.transaction_pool = list(not_processed_transactions) self.intermediate_transactions.clear() # DNS specific try: self._sync_auctions() # type: ignore except AttributeError: pass else: print_debug_info('Block not for new chain')
def load_chain(self): """ Loads Blockchain from the hard drive. """ if os.path.exists('bc_file.txt') and \ os.stat('bc_file.txt').st_size != 0 and \ Path('bc_file.txt').is_file(): print_debug_info('Load existing blockchain from file') with open('bc_file.txt', 'r') as bc_file: self.chain = serializer.deserialize(bc_file.read()) else: # If file doesn't exist / is empty: # Create genesis block self.chain[Header(0, 0, 768894480, 0, 0, 0)] = []
def validate_transaction(self, transaction: DDosTransaction): if not str(transaction.sender) in self.tree: print_debug_info('Sender could not be found in invited clients.') return False if not self.valid_operation(transaction): return False try: verify_key = nacl.signing.VerifyKey( transaction.sender, encoder=nacl.encoding.HexEncoder) transaction_hash = verify_key.verify( transaction.signature).decode() hash_str = (str(transaction.sender) + str(transaction.data) + str(transaction.timestamp)) validate_hash = self.hash(hash_str) if validate_hash == transaction_hash: print_debug_info('Signature OK') return True print_debug_info('Wrong Hash') return False except BadSignatureError: print_debug_info('Bad Signature, Validation Failed') return False
def parse_args(argv): """ Parse command line arguments Args: argv: Arguments from the command line. """ keystore_filename = 'keystore' port = 6666 signing_key = None dns = False try: opts, _ = getopt.getopt( argv[1:], 'hdnp=k=s=', ['help', 'debug', 'dns', 'port=', 'key=', 'store=']) for o, a in opts: if o in ('-h', '--help'): print('-d/--debug to enable debug prints') print('-n/--dns to start a dns chain') print('-p/--port to change default port') print('-k/--key to load a private key from a file') print('-s/--store to load a keystore from a file') sys.exit() if o in ('-d', '--debug'): set_debug() if o in ('-n', '--dns'): dns = True if o in ('-p', '--port'): try: port = int(a) except ValueError: print("Port was invalid (e.g. not an int)") elif o in ('-k', '--key'): try: signing_key = load_key(filename=a) print('Key successfully loaded') except Exception as e: print('Could not load Key / Key is invalid') print(e) elif o in ('-s', '--store'): keystore_filename = a print_debug_info(keystore_filename) except getopt.GetoptError as err: print('for help use --help') print(err) sys.exit() return keystore_filename, port, signing_key, dns
def resolve_conflict(self, new_chain: List[Header]): """ Resolves any conflicts that occur with different/outdated chains. Conflicts are resolved by accepting the longest valid chain. Args: new_chain: The chain to be validated, received from other nodes in the network. """ print_debug_info('Resolving conflict') if len(self.chain) < len(new_chain): if len(self.new_chain) < len(new_chain): # Validate new_chain old_header = new_chain[0] for header in new_chain[1:]: if self.validate_header(header, old_header): old_header = header else: print_debug_info('Conflict resolved (old chain)') return # Clear intermediate transactions self.intermediate_transactions.clear() # Create blockchain from new_chain new_bchain: OrderedDict[Header, List[Transaction]] = \ OrderedDict([(h, None) for h in new_chain]) # Add known blocks for h, t in self.chain.items(): if h in new_bchain: new_bchain[h] = t else: # Update intermediate transactions self.intermediate_transactions += t for h, t in self.new_chain.items(): if h in new_bchain: new_bchain[h] = t if t: for i_t in t: try: # Remove processed transactions self.intermediate_transactions.remove(i_t) except ValueError: pass self.new_chain = new_bchain print_debug_info('Conflict (Header) resolved (new chain)') # Ask for missing blocks for h, t in self.new_chain.items(): if t is None: self.send_queue.put(('get_block', h, 'broadcast')) else: print_debug_info('Conflict resolved (old chain)')
def validate_transaction(self, transaction: Transaction, new_chain: bool, mining: bool = False) -> bool: """ Validates a single transaction. Validates the signature, the signed hash of the signature and checks if the transaction amount is covered by the users balance. Args: transaction: The transaction to be validated. new_chain: Validate transaction for new chain? mining: If False, the function invalidates transaction, that are already in the transaction pool. If True, the function checks all transactions in the block being mined. Returns: The validity (True/False) of the transaction. """ if not transaction.amount > 0: print_debug_info('Received transaction with amount' + f' {transaction.amount} lower or equal to zero') return False if transaction in self.transaction_pool and not mining: return False # if transaction.sender == '0' and transaction.signature == '0': # return True try: verify_key = nacl.signing.VerifyKey( transaction.sender, encoder=nacl.encoding.HexEncoder) transaction_hash = verify_key.verify( transaction.signature).decode() validate_hash = hashlib.sha256( (str(transaction.sender) + str(transaction.recipient) + str(transaction.amount) + str(transaction.fee) + str(transaction.timestamp)).encode()).hexdigest() if validate_hash == transaction_hash: print_debug_info('Signature OK') return self.validate_balance(transaction) print_debug_info('Wrong Hash') return False except BadSignatureError: print_debug_info('Bad Signature, Validation Failed') return False
def new_transaction(self, transaction: Transaction): """ Add a new transaction to the blockchain. Args: transaction: Transaction that should be added. """ # Make sure, only one mining reward is granted per block for pool_transaction in self.transaction_pool: if pool_transaction.sender == '0' and \ pool_transaction.signature == '0': print_debug_info( 'This block already granted a mining transaction!') return if transaction in self.latest_block().transactions: return if self.validate_transaction(transaction, False): self.transaction_pool.append(transaction) self.send_queue.put(('new_transaction', transaction, 'broadcast')) if self.gui_ready: self.gui_queue.put(('new_transaction', transaction, 'local')) self.check_auction(transaction) else: print_debug_info('Invalid transaction')
def new_block(self, block: Block): """ Adds a provided block to the chain after checking it for validity. Args: block: The block to be added to the chain. """ # Check current chain if block.header.index == self.latest_block().header.index + 1: if self.validate_block(block, self.latest_block(), False): # remove transactions in new block from own transaction pool for block_transaction in block.transactions: if block_transaction in self.transaction_pool: self.transaction_pool.remove(block_transaction) self.send_queue.put(('new_header', block.header, 'broadcast')) self.chain[block.header] = block.transactions if self.gui_ready: self.gui_queue.put(('new_block', block, 'local')) else: print_debug_info('Block not for current chain') self.check_new_chain(block)
def validate_balance(self, transaction: Transaction): balance = self.check_balance(transaction.sender, transaction.timestamp) if balance >= transaction.amount + transaction.fee: print_debug_info('Balance sufficient, transaction is valid') return True print_debug_info('Balance insufficient, transaction is invalid') print_debug_info(f'Transaction at fault: {transaction} ' + f'was not covered by balance: {balance}') return False
def validate_header(self, header: Header, last_header: Header) -> bool: """ Validates a block-header. Args: header: Header that should be validated last_header: Header of current last block. """ # check if previous block == last_block if header.previous_hash != last_header.root_hash: return False # check order of time if header.timestamp < last_header.timestamp: return False # Check that version of the block can be processed if header.version > self.version: print_debug_info(f'Received block with version {header.version},' + ' but your current version is {self.version}.\n' + 'Check if there is a newer version available.') return False return True
def worker(send_queue: Queue, receive_queue: Queue, command_queue: Queue, gui_queue: Queue, port: int = 6666): """ Takes care of the communication between nodes. Args: send_queue: Queue for messages to other nodes. receive_queue: Queue for messages to the attached blockchain. """ print_debug_info("Started networking") # Example: # Find peers # Main loop: # - check send_queue (send new messages) # - check incoming messages # -- networking message (e.g. new peer, get peers) # -- Blockchain message: put on receive_queue SERVER.setup(port) PEERS.setup(send_queue, gui_queue, port) example_worker(send_queue, receive_queue, command_queue)
def new_header(self, header: Header): """ Check if new header is valid and ask for the corresponding block Args: header: New block-header """ if header.index > self.latest_header().index + 1: # block higher then current chain: # resolve conflict between chains self.send_queue.put(('get_chain', '', 'broadcast')) print_debug_info('Chain out-of-date.') print_debug_info('Updating...') return if self.validate_header(header, self.latest_header()): self.send_queue.put(('get_block', header, 'broadcast')) print_debug_info('Valid header, asked for full block') else: print_debug_info('Invalid header')
def blockchain_loop(blockchain: Blockchain, processor): """ The main loop of the blockchain thread. Receives messages and processes them. Args: blockchain: The blockchain upon which to operate. processor: Processor used to handle blockchain messages. """ while True: msg_type, msg_data, msg_address = receive_queue.get() print_debug_info('Processing: ' + msg_type) try: receive_msg(msg_type, msg_data, msg_address, blockchain, processor) except AssertionError as e: print_debug_info(f'Assertion Error on message\ {msg_type}:{msg_data}:{msg_address}') print_debug_info(e)
def new_transaction(self, transaction: DDosTransaction): if not self.validate_transaction(transaction): print_debug_info('Invalid transaction') return for pool_transaction in self.transaction_pool: if transaction == pool_transaction: print_debug_info('Transaction already in pool') return if transaction.data == pool_transaction.data: print_debug_info('This operation is already in the pool') return self.transaction_pool.append(transaction) self.send_queue.put(('new_transaction', transaction, 'broadcast')) if self.gui_ready: self.gui_queue.put(('new_transaction', transaction, 'local')) if len(self.transaction_pool) >= 5: self.create_m_blocks()
def validate_transaction(self, transaction: DNS_Transaction, new_chain: bool, mining: bool = False) -> bool: """ Validates a given transaction. Overwrites validate_transaction from pow_chain.py. Checks if a transaction is 1.) either a resolved auction or bid, and thus previously validated 2.) a valid domain operation (register, update, transfer, bid) or 3.) a valid 'normal' transaction """ normal_transaction = False valid_domain_operation = False # Resolved auctions, original auction as well as all bids are verified when first posted # Therefore we can simply accept these transactions if transaction.sender == '0' and transaction.signature == '1': return True if transaction.data.type == '': normal_transaction = True if transaction.amount == 0: return False if not normal_transaction: valid_domain_operation = self._is_valid_domain_transaction( transaction, new_chain) if not valid_domain_operation: return False if not mining: found = False for t in self.transaction_pool: if t == transaction: return False if normal_transaction: continue if t.data.domain_name == transaction.data.domain_name: found = True if transaction.data.type == 'r': return False if transaction.data.type == 'u' and t.sender != transaction.sender: return False if not found and transaction.data.type == 'u' and not valid_domain_operation: return False try: verify_key = nacl.signing.VerifyKey( transaction.sender, encoder=nacl.encoding.HexEncoder) transaction_hash = verify_key.verify( transaction.signature).decode() validate_hash = hashlib.sha256( (str(transaction.sender) + str(transaction.recipient) + str(transaction.amount) + str(transaction.fee) + str(transaction.timestamp) + str(transaction.data)).encode() ).hexdigest() if validate_hash == transaction_hash: print_debug_info('Signature OK') return self.validate_balance(transaction) print_debug_info('Wrong Hash') return False except BadSignatureError: print_debug_info('Bad Signature, Validation Failed') return False
def valid_operation(self, transaction: DDosTransaction): # Invite if transaction.data.type == 'i': if str(transaction.data.data) in self.tree: print_debug_info('Client is already invited!') return False # Uninvite / Purge elif transaction.data.type == 'ui' or transaction.data.type == 'p': if str(transaction.data.data) not in self.tree: print_debug_info('Client could not be found!') return False node_to_remove = self.tree.get_node_by_content( str(transaction.data.data)) sender_node = self.tree.get_node_by_content(str( transaction.sender)) if node_to_remove.content not in sender_node: print_debug_info('No permission to delete this node!') return False # Block IP-Address elif transaction.data.type == 'b': if transaction.data.data in self.blocked_ips: ancestors = self.blocked_ips[transaction.data.data].\ get_ancestors() if not str( transaction.sender) in [a.content for a in ancestors]: print_debug_info('IP was already blocked') return False # Unblock IP-Address elif transaction.data.type == 'ub': if transaction.data.data in self.blocked_ips: if str(transaction.sender) ==\ self.blocked_ips[transaction.data.data].content: return True ancestors = self.blocked_ips[transaction.data.data].\ get_ancestors() if str(transaction.sender) in [a.content for a in ancestors]: # IP blocked from descendant return True print_debug_info('IP was already blocked') return False else: print_debug_info('Trying to unblock IP that was not blocked') return False return True