code: def notify_txn_invalid(self, txn_id, message=None, extended_data=None):
          
          invalid_txn_info = {'id': txn_id}
          if message is not None:
              invalid_txn_info['message'] = message
          if extended_data is not None:
              invalid_txn_info['extended_data'] = extended_data
  
          with self._lock:
              for batch_id, txn_ids in self._batch_info.items():
                  if txn_id in txn_ids:
                      if batch_id not in self._invalid:
                          self._invalid[batch_id] = [invalid_txn_info]
                      else:
                          self._invalid[batch_id].append(invalid_txn_info)
                      self._pending.discard(batch_id)
                      self._update_observers(batch_id, ClientBatchStatus.INVALID)
                      return
docs: Adds a batch id to the invalid cache along with the id of the
          transaction that was rejected and any error message or extended data.
          Removes that batch id from the pending set. The cache is only
          temporary, and the batch info will be purged after one hour.
  
          Args:
              txn_id (str): The id of the invalid batch
              message (str, optional): Message explaining why batch is invalid
              extended_data (bytes, optional): Additional error data