def process_three_phase_msg(self, msg: ThreePhaseMsg, sender: str):
        """
        Process a 3-phase (pre-prepare, prepare and commit) request.
        Dispatch the request only if primary has already been decided, otherwise
        stash it.

        :param msg: the Three Phase message, one of PRE-PREPARE, PREPARE,
            COMMIT
        :param sender: name of the node that sent this message
        """
        sender = self.generateName(sender, self.instId)

        pp_key = ((msg.viewNo, msg.ppSeqNo) if
                  isinstance(msg, PrePrepare) else None)

        # the same PrePrepare might come here multiple times
        if (pp_key and (msg, sender) not in self.pre_prepare_tss[pp_key]):
            # TODO more clean solution would be to set timestamps
            # earlier (e.g. in zstack)
            self.pre_prepare_tss[pp_key][msg, sender] = self.get_time_for_3pc_batch()

        result, reason = self.validator.validate_3pc_msg(msg)
        if result == DISCARD:
            self.discard(msg, "{} discard message {} from {} "
                              "with the reason: {}".format(self, msg, sender, reason),
                         self.logger.trace)
        elif result == PROCESS:
            self.threePhaseRouter.handleSync((msg, sender))
        else:
            self.logger.debug("{} stashing 3 phase message {} with "
                              "the reason: {}".format(self, msg, reason))
            self.stasher.stash((msg, sender), result)
Process a 3-phase (pre-prepare, prepare and commit) request.
        Dispatch the request only if primary has already been decided, otherwise
        stash it.

        :param msg: the Three Phase message, one of PRE-PREPARE, PREPARE,
            COMMIT
        :param sender: name of the node that sent this message