Example #1
0
 def stop_broadcast_send_unconfirmed_block_timer():
     timer_key = TimerService.TIMER_KEY_BROADCAST_SEND_UNCONFIRMED_BLOCK
     timer_service = ObjectManager().channel_service.timer_service
     if timer_key in timer_service.timer_list:
         timer_service.stop_timer(timer_key)
Example #2
0
 def tearDown(self):
     # Blockchain을 삭제
     ObjectManager().peer_service = None
     leveldb.DestroyDB(self.db_name)
     os.system("rm -rf ./blockchain_db*")
Example #3
0
    async def consensus(self):
        util.logger.debug(f"-------------------consensus-------------------")
        async with self.__lock:
            if self._block_manager.epoch.leader_id != ChannelProperty(
            ).peer_id:
                util.logger.warning(
                    f"This peer is not leader. epoch leader={self._block_manager.epoch.leader_id}"
                )

            self._vote_queue = asyncio.Queue(loop=self._loop)
            complain_votes = self.__get_complaint_votes()
            complained_result = self._block_manager.epoch.complained_result
            if complained_result:
                self._blockchain.last_unconfirmed_block = None
            else:
                self._block_manager.epoch.remove_duplicate_tx_when_turn_to_leader(
                )

            last_block_vote_list = await self.__get_votes(
                self._blockchain.latest_block.header.hash)
            if last_block_vote_list is None:
                return

            last_unconfirmed_block: Optional[
                Block] = self._blockchain.last_unconfirmed_block
            last_block_header = self._blockchain.last_block.header

            if last_block_header.prep_changed:
                new_term = last_unconfirmed_block is None
            else:
                new_term = False

            if last_unconfirmed_block and not last_block_vote_list and not new_term:
                return

            # unrecorded_block means the last block of term to add prep changed block.
            if last_unconfirmed_block and last_unconfirmed_block.header.prep_changed:
                first_leader_of_term = self._blockchain.find_preps_ids_by_roothash(
                    last_unconfirmed_block.header.revealed_next_reps_hash)[0]
                is_unrecorded_block = ChannelProperty(
                ).peer_address != first_leader_of_term
            else:
                is_unrecorded_block = False

            skip_add_tx = is_unrecorded_block or complained_result
            block_builder = self._block_manager.epoch.makeup_block(
                complain_votes, last_block_vote_list, new_term, skip_add_tx)
            need_next_call = False
            try:
                if complained_result or new_term:
                    util.logger.spam(
                        "consensus block_builder.complained or new term")
                    """
                    confirm_info = self._blockchain.find_confirm_info_by_hash(self._blockchain.last_block.header.hash)
                    if not confirm_info and self._blockchain.last_block.header.height > 0:
                        util.logger.spam("Can't make a block as a leader, this peer will be complained too.")
                        return
                    """
                    block_builder = self._makeup_new_block(
                        block_builder.version, complain_votes,
                        self._blockchain.last_block.header.hash)
                elif self._blockchain.my_made_block_count == (
                        conf.MAX_MADE_BLOCK_COUNT - 2):
                    # (conf.MAX_MADE_BLOCK_COUNT - 2) means if made_block_count is 8,
                    # but after __add_block, it becomes 9
                    # so next unconfirmed block height is 10 (last).
                    if last_unconfirmed_block:
                        await self.__add_block(last_unconfirmed_block)
                    else:
                        util.logger.info(
                            f"This leader already made "
                            f"{self._blockchain.my_made_block_count} blocks. "
                            f"MAX_MADE_BLOCK_COUNT is {conf.MAX_MADE_BLOCK_COUNT} "
                            f"There is no more right. Consensus loop will return."
                        )
                        return
                elif len(block_builder.transactions) == 0 and not conf.ALLOW_MAKE_EMPTY_BLOCK and \
                        (last_unconfirmed_block and len(last_unconfirmed_block.body.transactions) == 0):
                    need_next_call = True
                elif last_unconfirmed_block:
                    await self.__add_block(last_unconfirmed_block)
            except (NotEnoughVotes, InvalidBlock):
                need_next_call = True
            except ThereIsNoCandidateBlock:
                util.logger.warning(f"There is no candidate block.")
                return
            finally:
                if need_next_call:
                    return self.__block_generation_timer.call()

            util.logger.spam(
                f"self._block_manager.epoch.leader_id: {self._block_manager.epoch.leader_id}"
            )
            candidate_block = self.__build_candidate_block(block_builder)
            candidate_block, invoke_results = self._blockchain.score_invoke(
                candidate_block,
                self._blockchain.latest_block,
                is_block_editable=True,
                is_unrecorded_block=is_unrecorded_block)

            util.logger.spam(f"candidate block : {candidate_block.header}")
            self._block_manager.candidate_blocks.add_block(
                candidate_block,
                self._blockchain.find_preps_addresses_by_header(
                    candidate_block.header))
            self.__broadcast_block(candidate_block)

            if is_unrecorded_block:
                self._blockchain.last_unconfirmed_block = None
            else:
                self._block_manager.vote_unconfirmed_block(
                    candidate_block, self._block_manager.epoch.round, True)
                self._blockchain.last_unconfirmed_block = candidate_block
                try:
                    await self._wait_for_voting(candidate_block)
                except NotEnoughVotes:
                    return

            if not candidate_block.header.prep_changed:
                if (self._blockchain.made_block_count_reached_max(
                        self._blockchain.last_block)
                        or self._block_manager.epoch.leader_id !=
                        ChannelProperty().peer_id):
                    ObjectManager().channel_service.reset_leader(
                        self._block_manager.epoch.leader_id)

            self.__block_generation_timer.call()
Example #4
0
    async def __get_votes(self, block_hash: Hash32):
        try:
            prev_votes = self._block_manager.candidate_blocks.get_votes(
                block_hash, self._block_manager.epoch.round)
        except KeyError as e:
            util.logger.spam(f"There is no block in candidates list: {e}")
            prev_votes = None

        if prev_votes:
            try:
                last_unconfirmed_block = self._blockchain.last_unconfirmed_block
                if last_unconfirmed_block is None:
                    warning_msg = f"There is prev_votes({prev_votes}). But I have no last_unconfirmed_block."
                    if self._blockchain.find_block_by_hash32(block_hash):
                        warning_msg += "\nBut already added block so  no longer have to wait for the vote."
                        # TODO An analysis of the cause of this situation is necessary.
                        util.logger.notice(warning_msg)
                        self._block_manager.candidate_blocks.remove_block(
                            block_hash)
                    else:
                        util.logger.warning(warning_msg)
                    return None

                self.__check_timeout(last_unconfirmed_block)
                if not prev_votes.is_completed():
                    self.__broadcast_block(last_unconfirmed_block)
                    if await self._wait_for_voting(last_unconfirmed_block
                                                   ) is None:
                        return None

                prev_votes_list = prev_votes.votes
            except TimeoutError:
                util.logger.warning(f"Timeout block of hash : {block_hash}")
                if self._block_manager.epoch.complained_result:
                    self._blockchain.last_unconfirmed_block = None
                self.stop_broadcast_send_unconfirmed_block_timer()
                ObjectManager().channel_service.state_machine.switch_role()
                return None
            except NotEnoughVotes:
                if last_unconfirmed_block:
                    util.logger.warning(
                        f"The last unconfirmed block has not enough votes. {block_hash}"
                    )
                    return None
                else:
                    util.exit_and_msg(
                        f"The block that has not enough votes added to the blockchain."
                    )
        else:
            prev_votes_dumped = self._blockchain.find_confirm_info_by_hash(
                block_hash)
            try:
                prev_votes_serialized = json.loads(prev_votes_dumped)
            except json.JSONDecodeError as e:  # handle exception for old votes
                util.logger.spam(f"{e}")
                prev_votes_list = []
            except TypeError as e:  # handle exception for not existing (NoneType) votes
                util.logger.spam(f"{e}")
                prev_votes_list = []
            else:
                version = self._blockchain.block_versioner.get_version(
                    self._block_manager.epoch.height)
                prev_votes_list = Votes.get_block_votes_class(
                    version).deserialize_votes(prev_votes_serialized)
        return prev_votes_list
Example #5
0
 def new_epoch(height: int, leader_id=None):
     leader_id = leader_id or ObjectManager(
     ).channel_service.block_manager.epoch.leader_id
     return Epoch(height, leader_id)
Example #6
0
    def ConnectPeer(self, request: loopchain_pb2.ConnectPeerRequest, context):
        """RadioStation 에 접속한다. 응답으로 기존의 접속된 Peer 목록을 받는다.

        :param request: PeerRequest
        :param context:
        :return: ConnectPeerReply
        """
        logging.info("Trying to connect peer: " + request.peer_id)

        res, info = ObjectManager().rs_service.validate_group_id(
            request.group_id)
        if res < 0:  # send null list(b'') while wrong input.
            return loopchain_pb2.ConnectPeerReply(
                status=message_code.Response.fail,
                peer_list=b'',
                more_info=info)

        # TODO check peer's authorization for channel
        channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if not request.channel else request.channel
        logging.debug(f"ConnectPeer channel_name({channel_name})")

        # channels: list = ObjectManager().rs_service.channel_manager.authorized_channels(request.peer_id)
        # if channel_name not in channels:
        #     return loopchain_pb2.ConnectPeerReply(
        #         status=message_code.Response.fail,
        #         peer_list=b'',
        #         more_info=f"channel({channel_name}) is not authorized for peer_id({request.peer_id})")

        logging.debug("Connect Peer " + "\nPeer_id : " + request.peer_id +
                      "\nGroup_id : " + request.group_id + "\nPeer_target : " +
                      request.peer_target)

        peer = PeerInfo(request.peer_id,
                        request.group_id,
                        request.peer_target,
                        PeerStatus.unknown,
                        cert=request.cert)

        util.logger.spam(f"service::ConnectPeer try add_peer")
        peer_order = ObjectManager(
        ).rs_service.channel_manager.get_peer_manager(channel_name).add_peer(
            peer)

        peer_list_dump = b''
        status, reason = message_code.get_response(message_code.Response.fail)

        if peer_order > 0:
            try:
                peer_list_dump = ObjectManager(
                ).rs_service.channel_manager.get_peer_manager(
                    channel_name).dump()
                status, reason = message_code.get_response(
                    message_code.Response.success)

            except pickle.PicklingError as e:
                logging.warning("fail peer_list dump")
                reason += " " + str(e)

        return loopchain_pb2.ConnectPeerReply(status=status,
                                              peer_list=peer_list_dump,
                                              channels=None,
                                              more_info=reason)
Example #7
0
    def __get_peer_stub_list(self):
        """It updates peer list for block manager refer to peer list on the loopchain network.
        This peer list is not same to the peer list of the loopchain network.

        :return max_height: a height of current blockchain
        :return peer_stubs: current peer list on the loopchain network
        """
        max_height = -1  # current max height
        unconfirmed_block_height = -1
        peer_stubs = []  # peer stub list for block height synchronization

        if not ObjectManager().channel_service.is_support_node_function(
                conf.NodeFunction.Vote):
            rest_stub = ObjectManager().channel_service.radio_station_stub
            peer_stubs.append(rest_stub)
            response = rest_stub.call("Status")
            height_from_status = int(json.loads(response.text)["block_height"])
            last_height = rest_stub.call("GetLastBlock").get('height')
            logging.debug(
                f"last_height: {last_height}, height_from_status: {height_from_status}"
            )
            max_height = max(height_from_status, last_height)
            unconfirmed_block_height = int(
                json.loads(response.text).get("unconfirmed_block_height", -1))
            return max_height, unconfirmed_block_height, peer_stubs

        # Make Peer Stub List [peer_stub, ...] and get max_height of network
        peer_target = ChannelProperty().peer_target
        peer_manager = ObjectManager().channel_service.peer_manager
        target_dict = peer_manager.get_IP_of_peers_dict()
        target_list = [
            peer_target for peer_id, peer_target in target_dict.items()
            if peer_id != ChannelProperty().peer_id
        ]

        for target in target_list:
            if target != peer_target:
                logging.debug(f"try to target({target})")
                channel = GRPCHelper().create_client_channel(target)
                stub = loopchain_pb2_grpc.PeerServiceStub(channel)
                try:
                    response = stub.GetStatus(
                        loopchain_pb2.StatusRequest(
                            request="",
                            channel=self.__channel_name,
                        ), conf.GRPC_TIMEOUT_SHORT)

                    response.block_height = max(
                        response.block_height,
                        response.unconfirmed_block_height)

                    if response.block_height > max_height:
                        # Add peer as higher than this
                        max_height = response.block_height
                        unconfirmed_block_height = response.unconfirmed_block_height
                        peer_stubs.append(stub)

                except Exception as e:
                    logging.warning(
                        f"This peer has already been removed from the block height target node. {e}"
                    )

        return max_height, unconfirmed_block_height, peer_stubs
Example #8
0
 def peer_service(self):
     return ObjectManager().peer_service
Example #9
0
 def tearDown(self):
     ObjectManager().peer_service = None
     ObjectManager().channel_service = None
Example #10
0
 def __init__(self):
     self.loopchain_objects = ObjectManager()
Example #11
0
 def tearDown(self):
     # Blockchain을 삭제
     ObjectManager().peer_service = None
     self.test_store.destroy_store()
     os.system("rm -rf ./blockchain_db*")
    def consensus(self):
        # broadcasting 한 블럭이 검증이 끝났는지 확인한다.
        confirmed_block = None
        try:
            confirmed_block = self._candidate_blocks.get_confirmed_block()
        except candidate_blocks.NoExistBlock as e:
            logging.error(e)
        except candidate_blocks.NotCompleteValidation as e:
            # try re count voters
            logging.warning(f"This block need more validation vote from Peers block "
                            f"hash({str(e.block.block_hash)}) channel({self._channel_name})")

            self._blockmanager.broadcast_audience_set()

            if util.diff_in_seconds(e.block.time_stamp) > conf.BLOCK_VOTE_TIMEOUT:
                # TODO vote 타임 아웃을 설정하고 타임 아웃 이내에 완성되지 않는 경우
                # 우선 해당 블럭은 버리는 것으로 임시 처리, 타임 아웃 블럭에 대한 정책 필요
                logging.warning("Time Outed Block not confirmed duration: " + str(util.diff_in_seconds(e.block.time_stamp)))

                self._candidate_blocks.remove_broken_block(e.block.block_hash)
            else:
                peer_service = ObjectManager().peer_service
                if peer_service is not None:
                    peer_service.reset_voter_count()

                self._candidate_blocks.reset_voter_count(str(e.block.block_hash))
                time.sleep(conf.INTERVAL_WAIT_PEER_VOTE)
        except candidate_blocks.InvalidatedBlock as e:
            # 실패한 투표에 대한 처리
            logging.error("InvalidatedBlock!! hash: " + str(e.block.block_hash))
            logging.debug("InvalidatedBlock!! prev_hash: " + str(e.block.prev_block_hash))

            # 현재 블록은 데이터가 있나?
            logging.debug("This block status: " + str(self._block.confirmed_transaction_list.__len__()))

            self.__throw_out_block(e.block)

        # 검증이 끝난 블럭이 있으면
        if confirmed_block is not None:
            logging.info(f"Block Validation is Complete "
                         f"hash({confirmed_block.block_hash}) channel({self._channel_name})")
            # 현재 블럭에 이전 투표에 대한 기록을 갱신한다.
            self._block.prev_block_confirm = True

            # 검증이 끝나면 BlockChain 에 해당 block 의 block_hash 로 등록 완료
            confirmed_block.block_status = BlockStatus.confirmed
            self._blockmanager.add_block(confirmed_block)

            # 새로운 블럭의 broadcast 를 위해 current_vote_block_hash 를 리셋한다.
            self._current_vote_block_hash = ""

        # logging.debug("current_vote_block_hash: " + current_vote_block_hash)
        # BlockChain 으로 부터 hash 를 받은 하나의 block 만 검증을 위해 broadcast 되어야 한다.
        # 하나의 block 이 검증 성공 또는 실패 시 current_vote_block_hash 는 "" 로 재설정 한다.
        if self._current_vote_block_hash == "":
            # block 에 수집된 tx 가 있으면
            if self._block is not None and self._block.confirmed_transaction_list.__len__() > 0:
                # 검증 받을 블록의 hash 를 생성하고 후보로 등록한다.
                # logging.warning("add unconfirmed block to candidate blocks")
                self._block.generate_block(self._candidate_blocks.get_last_block(self._blockchain))
                self._block.sign(ObjectManager().peer_service.auth)
                self._candidate_blocks.add_unconfirmed_block(self._block)

                # logging.warning("blockchain.last_block_hash: " + self._blockchain.last_block.block_hash)
                # logging.warning("block.block_hash: " + self._block.block_hash)
                # logging.warning("block.prev_block_hash: " + self._block.prev_block_hash)

                # 새로운 Block 을 생성하여 다음 tx 을 수집한다.
                self._gen_block()

            # 다음 검증 후보 블럭이 있는지 확인한다.
            candidate_block = self._candidate_blocks.get_candidate_block()
            peer_manager = ObjectManager().peer_service.channel_manager.get_peer_manager(self._channel_name)

            if candidate_block is not None:
                # 있으면 해당 블럭을 broadcast 하여 Peer 에게 검증을 요청한다.
                self._current_vote_block_hash = candidate_block.block_hash
                logging.info("candidate block hash: " + self._current_vote_block_hash)

                util.logger.spam(f"consensus_siever:consensus try peer_manager.get_next_leader_peer().peer_id")
                candidate_block.next_leader_peer = peer_manager.get_next_leader_peer().peer_id

                # 생성된 블럭을 투표 요청하기 위해서 broadcast 한다.
                self._blockmanager.broadcast_send_unconfirmed_block(candidate_block)

                # broadcast 를 요청했으면 다음 투표 block 이 있는지 계속 검사하기 위해 return 한다.
                return
            elif self._block is not None and \
                    (self._block.prev_block_confirm is True) and \
                    (self._block.confirmed_transaction_list.__len__() == 0):
                # logging.warning("broadcast voting block (has no tx but has a vote result)")

                # 검증할 후보 블럭이 없으면서 이전 블럭이 unconfirmed block 이면 투표가 담긴 빈 블럭을 전송한다.
                self._block.prev_block_hash = confirmed_block.block_hash
                self._block.block_type = BlockType.vote
                self.made_block_count -= 1

                logging.debug(f"made_block_count({self.made_block_count})")

                self._block.next_leader_peer = peer_manager.get_next_leader_peer().peer_id

                self._blockmanager.broadcast_send_unconfirmed_block(self._block)

                # 전송한 빈블럭을 대체한다.
                if self.made_block_count < conf.LEADER_BLOCK_CREATION_LIMIT:  # or not self._txQueue.empty():
                    self._gen_block()
                else:
                    # TODO LEADER_BLOCK_CREATION_LIMIT 에서 무조건 리더가 변경된다. 잔여 tx 처리가 필요하다.
                    self._stop_gen_block()
                    util.logger.spam(f"consensus_siever:consensus channel({self._channel_name}) "
                                     f"\ntry ObjectManager().peer_service.rotate_next_leader(self._channel_name)")
                    ObjectManager().peer_service.rotate_next_leader(self._channel_name)

        self._makeup_block()

        time.sleep(conf.SLEEP_SECONDS_IN_SERVICE_LOOP)
    def NotifyLeaderBroken(self, request, context):
        logging.debug("NotifyLeaderBroken: " + request.request)

        ObjectManager().peer_service.rotate_next_leader()
        return loopchain_pb2.CommonReply(response_code=message_code.Response.success, message="success")