예제 #1
0
    def __block_height_sync(self):
        # Make Peer Stub List [peer_stub, ...] and get max_height of network
        max_height, unconfirmed_block_height, peer_stubs = self.__get_peer_stub_list(
        )

        if self.__blockchain.last_unconfirmed_block is not None:
            self.candidate_blocks.remove_block(
                self.__blockchain.last_unconfirmed_block.header.hash)
        self.__blockchain.last_unconfirmed_block = None

        my_height = self.__current_block_height()
        logging.debug(
            f"in __block_height_sync max_height({max_height}), my_height({my_height})"
        )

        # prevent_next_block_mismatch until last_block_height in block DB. (excludes last_unconfirmed_block_height)
        self.get_blockchain().prevent_next_block_mismatch(
            self.__blockchain.block_height + 1)

        try:
            if peer_stubs:
                my_height, max_height = self.__block_request_to_peers_in_sync(
                    peer_stubs, my_height, unconfirmed_block_height,
                    max_height)
        except Exception as e:
            logging.warning(f"block_manager.py >>> block_height_sync :: {e}")
            traceback.print_exc()
            self.__start_block_height_sync_timer()
            return False

        curr_state = self.__channel_service.state_machine.state
        if curr_state != 'BlockSync':
            util.logger.info(f"Current state{curr_state} is not BlockSync")
            return True

        if my_height >= max_height:
            util.logger.debug(f"block_manager:block_height_sync is complete.")
            next_leader = self.__current_last_block().header.next_leader
            leader_peer = self.__channel_service.peer_manager.get_peer(
                next_leader.hex_hx()) if next_leader else None

            if leader_peer:
                self.__channel_service.peer_manager.set_leader_peer(
                    leader_peer, None)
                self.epoch = Epoch.new_epoch(leader_peer.peer_id)
            elif self.epoch.height < my_height:
                self.epoch = Epoch.new_epoch()

            self.__channel_service.state_machine.complete_sync()
        else:
            logging.warning(
                f"it's not completed block height synchronization in once ...\n"
                f"try block_height_sync again... my_height({my_height}) in channel({self.__channel_name})"
            )
            self.__channel_service.state_machine.block_sync()

        return True
예제 #2
0
    def __add_block(self, block: Block, vote: Vote = None):
        with self.__add_block_lock:
            invoke_results = self.__invoke_results.get(block.header.hash.hex(),
                                                       None)
            if invoke_results is None:
                if block.header.height == 0:
                    block, invoke_results = ObjectManager(
                    ).channel_service.genesis_invoke(block)
                else:
                    block, invoke_results = ObjectManager(
                    ).channel_service.score_invoke(block)

            try:
                self.__add_tx_to_block_db(block, invoke_results)
                ObjectManager().channel_service.score_write_precommit_state(
                    block)
            except Exception as e:
                logging.warning(f"blockchain:add_block FAIL "
                                f"channel_service.score_write_precommit_state")
                raise e
            finally:
                self.__invoke_results.pop(block.header.hash, None)

            next_total_tx = self.__write_block_data(block, vote)

            self.__last_block = block
            self.__block_height = self.__last_block.header.height
            self.__total_tx = next_total_tx
            logging.debug(
                f"blockchain add_block set block_height({self.__block_height}), "
                f"last_block({self.__last_block.header.hash.hex()})")
            logging.info(f"ADD BLOCK HEIGHT : {block.header.height} , "
                         f"HASH : {block.header.hash.hex()} , "
                         f"CHANNEL : {self.__channel_name}")
            logging.debug(f"ADDED BLOCK HEADER : {block.header}")

            util.apm_event(
                self.__peer_id, {
                    'event_type': 'AddBlock',
                    'peer_id': self.__peer_id,
                    'peer_name': conf.PEER_NAME,
                    'channel_name': self.__channel_name,
                    'data': {
                        'block_height': self.__block_height
                    }
                })

            # stop leader complain timer
            ObjectManager().channel_service.stop_leader_complain_timer()

            # start new epoch
            ObjectManager(
            ).channel_service.block_manager.epoch = Epoch.new_epoch(
                block.header.height + 1)

            # notify new block
            ObjectManager().channel_service.inner_service.notify_new_block()

            return True
예제 #3
0
    def confirm_prev_block(self, current_block: Block):
        self.__blockchain.confirm_prev_block(current_block)

        # stop leader complain timer
        self.__channel_service.stop_leader_complain_timer()

        # start new epoch
        self.epoch = Epoch.new_epoch()
예제 #4
0
    def reset_leader(self, new_leader_id, block_height=0, complained=False):
        """

        :param new_leader_id:
        :param block_height:
        :param complained:
        :return:
        """
        if self.peer_manager.get_leader_id(conf.ALL_GROUP_ID) == new_leader_id:
            return

        utils.logger.info(
            f"RESET LEADER channel({ChannelProperty().name}) leader_id({new_leader_id}), "
            f"complained={complained}")
        leader_peer = self.peer_manager.get_peer(new_leader_id, None)

        if block_height > 0 and block_height != self.block_manager.get_blockchain(
        ).last_block.header.height + 1:
            util.logger.warning(
                f"height behind peer can not take leader role. block_height({block_height}), "
                f"last_block.header.height("
                f"{self.block_manager.get_blockchain().last_block.header.height})"
            )
            return

        if leader_peer is None:
            logging.warning(
                f"in peer_service:reset_leader There is no peer by peer_id({new_leader_id})"
            )
            return

        util.logger.spam(
            f"peer_service:reset_leader target({leader_peer.target}), complained={complained}"
        )

        self_peer_object = self.peer_manager.get_peer(
            ChannelProperty().peer_id)
        self.peer_manager.set_leader_peer(leader_peer, None)
        if complained:
            self.block_manager.epoch.new_round(leader_peer.peer_id)
        else:
            self.block_manager.epoch = Epoch.new_epoch(leader_peer.peer_id)
        logging.info(
            f"Epoch height({self.block_manager.epoch.height}), leader ({self.block_manager.epoch.leader_id})"
        )

        if self_peer_object.peer_id == leader_peer.peer_id:
            logging.debug("Set Peer Type Leader!")
            peer_type = loopchain_pb2.BLOCK_GENERATOR
            self.state_machine.turn_to_leader()
        else:
            logging.debug("Set Peer Type Peer!")
            peer_type = loopchain_pb2.PEER
            self.state_machine.turn_to_peer()

        self.block_manager.set_peer_type(peer_type)
예제 #5
0
    async def __add_block_and_new_epoch(self, block_builder,
                                        last_unconfirmed_block: Block):
        """Add Block and start new epoch

        :param block_builder:
        :param last_unconfirmed_block:
        :return: next leader
        """
        await self.__add_block(last_unconfirmed_block)
        self.__remove_duplicate_tx_when_turn_to_leader(block_builder,
                                                       last_unconfirmed_block)
        self._block_manager.epoch = Epoch.new_epoch(ChannelProperty().peer_id)
        return last_unconfirmed_block.header.next_leader
예제 #6
0
    def confirm_prev_block(self, current_block: Block):
        try:
            self.__blockchain.confirm_prev_block(current_block)

            # stop leader complain timer
            self.__channel_service.stop_leader_complain_timer()

            # start new epoch
            self.epoch = Epoch.new_epoch()
        except BlockchainError as e:
            logging.warning(
                f"BlockchainError while confirm_block({e}), retry block_height_sync"
            )
            self.block_height_sync()
예제 #7
0
    def confirm_prev_block(self, current_block: Block):
        confirmed_block = self.__blockchain.confirm_prev_block(current_block)
        if confirmed_block is None:
            return

        # stop leader complain timer
        self.__channel_service.stop_leader_complain_timer()

        # start new epoch
        if not current_block.header.complained:
            self.epoch = Epoch.new_epoch()

        # reset leader
        self.__channel_service.reset_leader(
            current_block.header.next_leader.hex_hx())
예제 #8
0
    async def consensus(self):
        util.logger.debug(
            f"-------------------consensus "
            f"candidate_blocks({len(self._block_manager.candidate_blocks.blocks)})"
        )
        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}"
                )
                return

            self._vote_queue = asyncio.Queue(loop=self._loop)

            complained_result = self._block_manager.epoch.complained_result
            block_builder = self._block_manager.epoch.makeup_block(
                complained_result)
            vote_result = None
            last_unconfirmed_block = self._blockchain.last_unconfirmed_block
            next_leader = ExternalAddress.fromhex(ChannelProperty().peer_id)

            need_next_call = False
            try:
                if complained_result:
                    util.logger.spam("consensus block_builder.complained")
                    """
                    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
                    """
                    self._made_block_count += 1
                elif self.made_block_count >= (conf.MAX_MADE_BLOCK_COUNT - 1):
                    if last_unconfirmed_block:
                        await self.__add_block(last_unconfirmed_block)
                        peer_manager = ObjectManager(
                        ).channel_service.peer_manager
                        next_leader = ExternalAddress.fromhex(
                            peer_manager.get_next_leader_peer(
                                current_leader_peer_id=ChannelProperty(
                                ).peer_id).peer_id)
                    else:
                        util.logger.info(
                            f"This leader already made {self.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 or conf.ALLOW_MAKE_EMPTY_BLOCK:
                    if last_unconfirmed_block:
                        next_leader = await self.__add_block_and_new_epoch(
                            block_builder, last_unconfirmed_block)
                elif len(block_builder.transactions) == 0 and (
                        last_unconfirmed_block
                        and len(last_unconfirmed_block.body.transactions) > 0):
                    next_leader = await self.__add_block_and_new_epoch(
                        block_builder, last_unconfirmed_block)
                else:
                    need_next_call = True
            except NotEnoughVotes:
                need_next_call = True
            finally:
                if need_next_call:
                    return self.__block_generation_timer.call()

            candidate_block = self.__build_candidate_block(
                block_builder, next_leader, vote_result)
            candidate_block, invoke_results = ObjectManager(
            ).channel_service.score_invoke(candidate_block)
            self._block_manager.set_invoke_results(
                candidate_block.header.hash.hex(), invoke_results)

            util.logger.spam(f"candidate block : {candidate_block.header}")

            self._block_manager.vote_unconfirmed_block(
                candidate_block.header.hash, True)
            self._block_manager.candidate_blocks.add_block(candidate_block)
            self._blockchain.last_unconfirmed_block = candidate_block

            broadcast_func = partial(
                self._block_manager.broadcast_send_unconfirmed_block,
                candidate_block)
            self.__start_broadcast_send_unconfirmed_block_timer(broadcast_func)
            if await self._wait_for_voting(candidate_block) is None:
                return

            if next_leader.hex_hx() != ChannelProperty().peer_id:
                util.logger.spam(f"-------------------turn_to_peer "
                                 f"next_leader({next_leader.hex_hx()}) "
                                 f"peer_id({ChannelProperty().peer_id})")
                ObjectManager().channel_service.reset_leader(
                    next_leader.hex_hx())
                ObjectManager().channel_service.turn_on_leader_complain_timer()
            else:
                self._block_manager.epoch = Epoch.new_epoch(
                    next_leader.hex_hx())
                if not conf.ALLOW_MAKE_EMPTY_BLOCK:
                    self.__block_generation_timer.call_instantly()
                else:
                    self.__block_generation_timer.call()
예제 #9
0
    async def consensus(self):
        util.logger.debug(
            f"-------------------consensus "
            f"candidate_blocks({len(self._block_manager.candidate_blocks.blocks)})"
        )
        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}"
                )
                return

            self._vote_queue = asyncio.Queue(loop=self._loop)

            complained_result = self._block_manager.epoch.complained_result
            block_builder = self._block_manager.epoch.makeup_block(
                complained_result)
            vote_result = None
            last_unconfirmed_block = self._blockchain.last_unconfirmed_block
            next_leader = ExternalAddress.fromhex(ChannelProperty().peer_id)

            if complained_result:
                util.logger.spam("consensus block_builder.complained")
                """
                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
                """

                self._made_block_count += 1
            elif len(block_builder.transactions) > 0:
                util.logger.spam(
                    f"consensus len(block_builder.transactions) > 0")
                if last_unconfirmed_block:
                    if (len(last_unconfirmed_block.body.transactions) > 0
                            or last_unconfirmed_block.header.complained) or (
                                len(last_unconfirmed_block.body.transactions)
                                == 0 and
                                last_unconfirmed_block.header.peer_id.hex_hx()
                                != ChannelProperty().peer_id):
                        vote = self._block_manager.candidate_blocks.get_vote(
                            last_unconfirmed_block.header.hash)
                        vote_result = await self._wait_for_voting(
                            last_unconfirmed_block)
                        if not vote_result:
                            return self.__block_generation_timer.call()

                        self.__add_block(last_unconfirmed_block, vote)
                        self._block_manager.epoch = Epoch.new_epoch(
                            ChannelProperty().peer_id)

                        next_leader = last_unconfirmed_block.header.next_leader
            else:
                if (last_unconfirmed_block) and (
                        len(last_unconfirmed_block.body.transactions) > 0
                        or last_unconfirmed_block.header.complained):
                    vote = self._block_manager.candidate_blocks.get_vote(
                        last_unconfirmed_block.header.hash)
                    vote_result = await self._wait_for_voting(
                        last_unconfirmed_block)
                    if not vote_result:
                        return self.__block_generation_timer.call()

                    self.__add_block(last_unconfirmed_block, vote)

                    peer_manager = ObjectManager().channel_service.peer_manager
                    next_leader = ExternalAddress.fromhex(
                        peer_manager.get_next_leader_peer(
                            current_leader_peer_id=ChannelProperty().peer_id).
                        peer_id)
                else:
                    return self.__block_generation_timer.call()

            candidate_block = self.__build_candidate_block(
                block_builder, next_leader, vote_result)
            candidate_block, invoke_results = ObjectManager(
            ).channel_service.score_invoke(candidate_block)
            self._block_manager.set_invoke_results(
                candidate_block.header.hash.hex(), invoke_results)

            util.logger.spam(f"candidate block : {candidate_block.header}")

            self._block_manager.vote_unconfirmed_block(
                candidate_block.header.hash, True)
            self._block_manager.candidate_blocks.add_block(candidate_block)
            self._blockchain.last_unconfirmed_block = candidate_block

            broadcast_func = partial(
                self._block_manager.broadcast_send_unconfirmed_block,
                candidate_block)
            self.__start_broadcast_send_unconfirmed_block_timer(broadcast_func)
            if await self._wait_for_voting(candidate_block) is None:
                return

            if len(candidate_block.body.transactions) == 0 and not conf.ALLOW_MAKE_EMPTY_BLOCK and \
                    next_leader.hex_hx() != ChannelProperty().peer_id:
                util.logger.spam(f"-------------------turn_to_peer "
                                 f"next_leader({next_leader.hex_hx()}) "
                                 f"peer_id({ChannelProperty().peer_id})")
                ObjectManager().channel_service.reset_leader(
                    next_leader.hex_hx())
            else:
                self._block_manager.epoch = Epoch.new_epoch(
                    next_leader.hex_hx())
                if not conf.ALLOW_MAKE_EMPTY_BLOCK:
                    self.__block_generation_timer.call_instantly()
                else:
                    self.__block_generation_timer.call()