docs: def handleUpgradeTxn(self, txn) -> None:
          """
          Handles transaction of type POOL_UPGRADE
          Can schedule or cancel upgrade to a newer
          version at specified time
  
          :param txn:
          """
          FINALIZING_EVENT_TYPES = [UpgradeLog.Events.succeeded, UpgradeLog.Events.failed]
  
          if get_type(txn) != POOL_UPGRADE:
              return
  
          logger.info("Node '{}' handles upgrade txn {}".format(self.nodeName, txn))
          txn_data = get_payload_data(txn)
          action = txn_data[ACTION]
          version = txn_data[VERSION]
          justification = txn_data.get(JUSTIFICATION)
          pkg_name = txn_data.get(PACKAGE, self.config.UPGRADE_ENTRY)
          upgrade_id = self.get_action_id(txn)
  
          # TODO test
          try:
              version = src_version_cls(pkg_name)(version)
          except InvalidVersionError as exc:
              logger.warning(
                  "{} can't handle upgrade txn with version {} for package {}: {}"
                  .format(self, version, pkg_name, exc)
              )
              return
  
          if action == START:
              # forced txn could have partial schedule list
              if self.nodeId not in txn_data[SCHEDULE]:
                  logger.info("Node '{}' disregards upgrade txn {}".format(
                      self.nodeName, txn))
                  return
  
              last_event = self.lastActionEventInfo
              if last_event:
                  if (last_event.data.upgrade_id == upgrade_id and
                          last_event.ev_type in FINALIZING_EVENT_TYPES):
                      logger.info(
                          "Node '{}' has already performed an upgrade with upgrade_id {}. "
                          "Last recorded event is {}"
                          .format(self.nodeName, upgrade_id, last_event.data))
                      return
  
              when = txn_data[SCHEDULE][self.nodeId]
              failTimeout = txn_data.get(TIMEOUT, self.defaultActionTimeout)
  
              if isinstance(when, str):
                  when = dateutil.parser.parse(when)
  
              new_ev_data = UpgradeLogData(when, version, upgrade_id, pkg_name)
  
              if self.scheduledAction:
                  if self.scheduledAction == new_ev_data:
                      logger.debug(
                          "Node {} already scheduled upgrade to version '{}' "
                          .format(self.nodeName, version))
                      return
                  else:
                      logger.info(
                          "Node '{}' cancels previous upgrade and schedules a new one to {}"
                          .format(self.nodeName, version))
                      self._cancelScheduledUpgrade(justification)
  
              logger.info("Node '{}' schedules upgrade to {}".format(self.nodeName, version))
  
              self._scheduleUpgrade(new_ev_data, failTimeout)
              return
  
          if action == CANCEL:
              if (self.scheduledAction and
                      self.scheduledAction.version == version):
                  self._cancelScheduledUpgrade(justification)
                  logger.info("Node '{}' cancels upgrade to {}".format(
                      self.nodeName, version))
              return
  
          logger.error(
              "Got {} transaction with unsupported action {}".format(
                  POOL_UPGRADE, action))