async def async_getheaders(self, param): """ Async. Return 20 latest block headers. :param param: (string) empty: last 20 blocks headers. start_height,count or ,count (last N headers) :return: """ try: protocmd = commands_pb2.Command() protocmd.Clear() protocmd.command = commands_pb2.Command.getheaders if param is None or param == 'None': param = '' if param == '': blocks = await self.async_fetchall(SQL_BLOCKS_LAST, (config.BLOCK_SYNC_COUNT,)) else: start, count = param.split(',') if '' == start: blocks = await self.async_fetchall(SQL_BLOCKS_LAST, (count,)) else: blocks = await self.async_fetchall(SQL_BLOCKS_SYNC, (start, count)) for block in blocks: block = PosBlock().from_dict(dict(block)) block.add_to_proto(protocmd) return protocmd except Exception as e: self.app_log.error("SRV: async_getheaders: Error {}".format(e)) exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] self.app_log.error('detail {} {} {}'.format(exc_type, fname, exc_tb.tb_lineno)) raise
async def async_getblock(self, a_height): """ Command id 14 returns the block of the given height. :param a_height: int :return: protocmd with the block if exists or None """ try: protocmd = commands_pb2.Command() protocmd.Clear() protocmd.command = commands_pb2.Command.getblock block = await self.async_fetchone(SQL_HEIGHT_BLOCK, (a_height,), as_dict=True) if not block: return protocmd block = PosBlock().from_dict(dict(block)) # Add the block txs txs = await self.async_fetchall(SQL_TXS_FOR_HEIGHT, (block.height,)) for tx in txs: tx = PosMessage().from_dict(dict(tx)) block.txs.append(tx) block.add_to_proto(protocmd) return protocmd except Exception as e: self.app_log.error("SRV: async_getblock: Error {}".format(e)) exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] self.app_log.error('detail {} {} {}'.format(exc_type, fname, exc_tb.tb_lineno)) raise
async def async_roundblocks(self, a_round): """ Command id 13 returns all blocks of the given round. FR: Harmonize. this one needs a proto as output (proto command with list of blocks) :param a_round: :return: protocmd with all blocks """ try: protocmd = commands_pb2.Command() protocmd.Clear() protocmd.command = commands_pb2.Command.roundblocks blocks = await self.async_fetchall(SQL_ROUND_BLOCKS, (a_round, )) for block in blocks: block = PosBlock().from_dict(dict(block)) # Add the block txs txs = await self.async_fetchall(SQL_TXS_FOR_HEIGHT, (block.height, )) for tx in txs: tx = PosMessage().from_dict(dict(tx)) block.txs.append(tx) block.add_to_proto(protocmd) # check block integrity if len(txs) != block.msg_count: self.app_log.error( "Only {} tx for block {} instead of {} announced". format(len(txs), block.height, block.msg_count)) com_helpers.MY_NODE.stop() return protocmd except Exception as e: self.app_log.error("SRV: async_roundblocks: Error {}".format(e)) exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] self.app_log.error('detail {} {} {}'.format( exc_type, fname, exc_tb.tb_lineno)) raise
async def async_blocksync(self, height): """ returns N blocks starting with the given height. FR: Harmonize. this one needs a proto as output (proto command with list of blocks) :param height: :return: """ try: protocmd = commands_pb2.Command() protocmd.Clear() protocmd.command = commands_pb2.Command.blocksync blocks = await self.async_fetchall( SQL_BLOCKS_SYNC, (height, config.BLOCK_SYNC_COUNT)) for block in blocks: block = PosBlock().from_dict(dict(block)) # Add the block txs txs = await self.async_fetchall(SQL_TXS_FOR_HEIGHT, (block.height, )) for tx in txs: tx = PosMessage().from_dict(dict(tx)) block.txs.append(tx) # check block integrity if len(txs) != block.msg_count: self.app_log.error( "Only {} tx for block {} instead of {} announced". format(len(txs), height, block.msg_count)) com_helpers.MY_NODE.stop() block.add_to_proto(protocmd) #print(protocmd) return protocmd except Exception as e: self.app_log.error("SRV: async_blocksync: Error {}".format(e)) exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] self.app_log.error('detail {} {} {}'.format( exc_type, fname, exc_tb.tb_lineno)) raise
def genesis_block(self): """ Build up genesis block info :return: """ # No tx for genesis txids = [] block_dict = {'height': 0, 'round': 0, 'sir': 0, 'timestamp': config.ORIGIN_OF_TIME, 'previous_hash': poscrypto.blake(config.GENESIS_SEED.encode('utf-8')).digest(), 'msg_count': 0, 'uniques_sources': 0, 'txs': txids, 'forger': config.GENESIS_ADDRESS, 'block_hash': b'', 'signature': b''} # print(block_dict) block = PosBlock().from_dict(block_dict) # print(block.to_json()) block.sign() print(block.to_json()) if self.verbose: print(block.to_json()) return block
async def digest_block(self, proto_block, from_miner=False, relaxed_checks=False): """ Checks if the block is valid and saves it :param proto_block: a protobuf 'block' object :param from_miner: True if came from a live miner (current slot) :param relaxed_checks: True if we want light checks (like our own block) :return: """ try: block_from = 'from Peer' if from_miner: block_from = 'from Miner' if relaxed_checks: block_from += ' (Relaxed checks)' # Avoid re-entrance if self.inserting_block: self.app_log.warning("Digestion of block {} aborted".format(block_from)) return # print(">> protoblock", proto_block) block = PosBlock().from_proto(proto_block) # print(">> dictblock", block.to_dict()) if 'txdigest' in config.LOG: self.app_log.warning("Digesting block {} {} : {}".format(block.height, block_from, block.to_json())) else: self.app_log.warning("Digesting block {} {} : {} txs, {} uniques sources.".format(block.height, block_from, len(block.txs), block.uniques_sources)) # Good height? - FR: harmonize, use objects everywhere? if block.height != self.block['height'] + 1: self.app_log.warning("Digesting block {} : bad height, our current height is {}" .format(block.height, self.block['height'])) return False # Good hash? if block.previous_hash != self.block['block_hash']: self.app_log.warning("Digesting block {} : bad hash {} vs our {}" .format(block.height, block.previous_hash, self.block['block_hash'])) return False if block.msg_count != len(block.txs): self.app_log.warning("Digesting block {} : {} txs reported but {} included" .format(block.height, block.msg_count, len(block.txs))) return False # TODO: more checks # TODO: if from miner, make sure we refreshed the round first. # timestamp of blocks # fits with current round? # TODO : only tx from valid HNs (registered for that round?) # recount uniques_sources? # msg_count = tx# ? # right juror? # Checks will depend on from_miner (state = sync) or not (relaxed checks when catching up) # see also tx checks from mempool. Maybe lighter self.inserting_block = True await self._insert_block(block) self.app_log.warning("Digested block {}".format(block.height)) return True except Exception as e: self.app_log.error("digest_block Error {}".format(e)) exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] self.app_log.error('detail {} {} {}'.format(exc_type, fname, exc_tb.tb_lineno)) return False finally: self.inserting_block = False