def get_all_block_headers_iter(self, branch=0): # yields tuples of BlockHeader, branch, height sql = 'SELECT * FROM blocks WHERE branch={}'.format(branch) with sqlite3.connect(self.CHAIN_DB) as conn: cursor = conn.cursor() cursor.execute(sql) for block in cursor: yield BlockHeader(block[1], block[2], block[5], block[4], block[6]), block[7], block[3]
def get_block_header_by_hash(self, block_hash): # returns tuple of BlockHeader, branch, height sql = "SELECT * FROM blocks WHERE hash='{}'".format(block_hash) with sqlite3.connect(self.CHAIN_DB) as conn: cursor = conn.cursor() cursor.execute(sql) block = cursor.fetchone() if block is None: return None return BlockHeader(block[1], block[2], block[5], block[4], block[6]), block[7], block[3]
def get_tallest_block_header(self, branch=0): # returns tuple of BlockHeader, branch, height sql = 'SELECT * FROM blocks WHERE height = (SELECT MAX(height) FROM blocks WHERE branch={})'.format( branch) with sqlite3.connect(self.CHAIN_DB) as conn: cursor = conn.cursor() cursor.execute(sql) block = cursor.fetchone() if block is None: return None return BlockHeader(block[1], block[2], block[5], block[4], block[6]), block[7], block[3]
def get_block_headers_range_iter(self, start_height, stop_height, branch=0): # yields tuples of BlockHeader, branch, height sql = 'SELECT * FROM blocks WHERE height >= {} AND height <= {} AND branch={} ORDER BY height ASC'\ .format(start_height, stop_height, branch) with sqlite3.connect(self.CHAIN_DB) as conn: cursor = conn.cursor() cursor.execute(sql) for block in cursor: yield BlockHeader(block[1], block[2], block[5], block[4], block[6]), block[7], block[3]
def get_block_headers_by_height(self, height): # returns tuples of BlockHeader, branch, height block_headers = [] sql = 'SELECT * FROM blocks WHERE height={} ORDER BY branch'.format( height) with sqlite3.connect(self.CHAIN_DB) as conn: cursor = conn.cursor() cursor.execute(sql) for block in cursor: block_headers.append( (BlockHeader(block[1], block[2], block[5], block[4], block[6]), block[7], block[3])) return block_headers
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
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