github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/validator/sawtooth_validator/journal/responder.py (about)

     1  # Copyright 2017 Intel Corporation
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #     http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  # ------------------------------------------------------------------------------
    15  
    16  import logging
    17  from threading import RLock
    18  
    19  from sawtooth_validator.networking.dispatch import Handler
    20  from sawtooth_validator.networking.dispatch import HandlerResult
    21  from sawtooth_validator.networking.dispatch import HandlerStatus
    22  from sawtooth_validator.journal.timed_cache import TimedCache
    23  from sawtooth_validator.protobuf import network_pb2
    24  from sawtooth_validator.protobuf import validator_pb2
    25  
    26  LOGGER = logging.getLogger(__name__)
    27  
    28  CACHE_KEEP_TIME = 300
    29  
    30  
    31  class Responder(object):
    32      def __init__(self,
    33                   completer,
    34                   cache_keep_time=300,
    35                   cache_purge_frequency=30):
    36          self.completer = completer
    37          self.pending_requests = TimedCache(cache_keep_time,
    38                                             cache_purge_frequency)
    39          self._lock = RLock()
    40  
    41      def check_for_block(self, block_id):
    42          # Ask Completer
    43          if block_id == "HEAD":
    44              block = self.completer.get_chain_head()
    45          else:
    46              block = self.completer.get_block(block_id)
    47          return block
    48  
    49      def check_for_batch(self, batch_id):
    50          batch = self.completer.get_batch(batch_id)
    51          return batch
    52  
    53      def check_for_batch_by_transaction(self, transaction_id):
    54          batch = self.completer.get_batch_by_transaction(transaction_id)
    55          return batch
    56  
    57      def already_requested(self, requested_id):
    58          with self._lock:
    59              if requested_id in self.pending_requests:
    60                  return True
    61              return False
    62  
    63      def add_request(self, requested_id, connection_id):
    64          with self._lock:
    65              if requested_id in self.pending_requests:
    66                  if connection_id not in self.pending_requests[requested_id]:
    67                      self.pending_requests[requested_id] += [connection_id]
    68  
    69              else:
    70                  self.pending_requests[requested_id] = [connection_id]
    71  
    72      def get_request(self, requested_id):
    73          with self._lock:
    74              return self.pending_requests.get(requested_id)
    75  
    76      def remove_request(self, requested_id):
    77          with self._lock:
    78              if requested_id in self.pending_requests:
    79                  del self.pending_requests[requested_id]
    80  
    81  
    82  class BlockResponderHandler(Handler):
    83      def __init__(self, responder, gossip):
    84          self._responder = responder
    85          self._gossip = gossip
    86          self._seen_requests = TimedCache(CACHE_KEEP_TIME)
    87  
    88      def handle(self, connection_id, message_content):
    89          block_request_message = network_pb2.GossipBlockRequest()
    90          block_request_message.ParseFromString(message_content)
    91          if block_request_message.nonce in self._seen_requests:
    92              LOGGER.debug("Received repeat GossipBlockRequest from %s",
    93                           connection_id)
    94  
    95              return HandlerResult(HandlerStatus.DROP)
    96  
    97          block_id = block_request_message.block_id
    98          block = self._responder.check_for_block(block_id)
    99          if block is None:
   100              # No block found, broadcast original message to other peers
   101              # and add to pending requests
   102              if block_id == "HEAD":
   103                  LOGGER.debug("No chain head available. Cannot respond to block"
   104                               " requests.")
   105              else:
   106                  if not self._responder.already_requested(block_id):
   107                      if block_request_message.time_to_live > 0:
   108                          time_to_live = block_request_message.time_to_live
   109                          block_request_message.time_to_live = time_to_live - 1
   110                          self._gossip.broadcast(
   111                              block_request_message,
   112                              validator_pb2.Message.GOSSIP_BLOCK_REQUEST,
   113                              exclude=[connection_id])
   114  
   115                          self._seen_requests[block_request_message.nonce] = \
   116                              block_request_message.block_id
   117  
   118                          self._responder.add_request(block_id, connection_id)
   119                  else:
   120                      LOGGER.debug("Block %s has already been requested",
   121                                   block_id)
   122  
   123                      self._responder.add_request(block_id, connection_id)
   124          else:
   125              LOGGER.debug("Responding to block requests: %s",
   126                           block.get_block().header_signature)
   127  
   128              block_response = network_pb2.GossipBlockResponse(
   129                  content=block.get_block().SerializeToString())
   130  
   131              self._gossip.send(validator_pb2.Message.GOSSIP_BLOCK_RESPONSE,
   132                                block_response.SerializeToString(),
   133                                connection_id)
   134  
   135          return HandlerResult(HandlerStatus.PASS)
   136  
   137  
   138  class ResponderBlockResponseHandler(Handler):
   139      def __init__(self, responder, gossip):
   140          self._responder = responder
   141          self._gossip = gossip
   142  
   143      def handle(self, connection_id, message_content):
   144          block, message_content = message_content
   145  
   146          open_request = self._responder.get_request(block.header_signature)
   147  
   148          if open_request is None:
   149              return HandlerResult(status=HandlerStatus.PASS)
   150  
   151          for connection in open_request:
   152              LOGGER.debug("Responding to block request: Send %s to %s",
   153                           block.header_signature,
   154                           connection)
   155              try:
   156                  self._gossip.send(validator_pb2.Message.GOSSIP_BLOCK_RESPONSE,
   157                                    message_content,
   158                                    connection)
   159              except ValueError:
   160                  LOGGER.debug("Can't send block response %s to closed "
   161                               "connection %s",
   162                               block.header_signature,
   163                               connection)
   164  
   165          self._responder.remove_request(block.header_signature)
   166  
   167          return HandlerResult(HandlerStatus.PASS)
   168  
   169  
   170  class BatchByBatchIdResponderHandler(Handler):
   171      def __init__(self, responder, gossip):
   172          self._responder = responder
   173          self._gossip = gossip
   174          self._seen_requests = TimedCache(CACHE_KEEP_TIME)
   175  
   176      def handle(self, connection_id, message_content):
   177          batch_request_message = network_pb2.GossipBatchByBatchIdRequest()
   178          batch_request_message.ParseFromString(message_content)
   179          if batch_request_message.nonce in self._seen_requests:
   180              LOGGER.debug("Received repeat GossipBatchByBatchIdRequest from %s",
   181                           connection_id)
   182              return HandlerResult(HandlerStatus.DROP)
   183  
   184          batch = None
   185          batch = self._responder.check_for_batch(batch_request_message.id)
   186  
   187          if batch is None:
   188              # No batch found, broadcast original message to other peers
   189              # and add to pending requests
   190              if not self._responder.already_requested(batch_request_message.id):
   191  
   192                  if batch_request_message.time_to_live > 0:
   193                      time_to_live = batch_request_message.time_to_live
   194                      batch_request_message.time_to_live = time_to_live - 1
   195                      self._gossip.broadcast(
   196                          batch_request_message,
   197                          validator_pb2.Message.GOSSIP_BATCH_BY_BATCH_ID_REQUEST,
   198                          exclude=[connection_id])
   199  
   200                      self._seen_requests[batch_request_message.nonce] = \
   201                          batch_request_message.id
   202  
   203                      self._responder.add_request(batch_request_message.id,
   204                                                  connection_id)
   205              else:
   206                  LOGGER.debug("Batch %s has already been requested",
   207                               batch_request_message.id)
   208  
   209                  self._responder.add_request(batch_request_message.id,
   210                                              connection_id)
   211          else:
   212              LOGGER.debug("Responding to batch requests %s",
   213                           batch.header_signature)
   214  
   215              batch_response = network_pb2.GossipBatchResponse(
   216                  content=batch.SerializeToString(),
   217              )
   218  
   219              self._gossip.send(validator_pb2.Message.GOSSIP_BATCH_RESPONSE,
   220                                batch_response.SerializeToString(),
   221                                connection_id)
   222  
   223          return HandlerResult(HandlerStatus.PASS)
   224  
   225  
   226  class BatchByTransactionIdResponderHandler(Handler):
   227      def __init__(self, responder, gossip):
   228          self._responder = responder
   229          self._gossip = gossip
   230          self._seen_requests = TimedCache(CACHE_KEEP_TIME)
   231  
   232      def handle(self, connection_id, message_content):
   233          batch_request_message = network_pb2.GossipBatchByTransactionIdRequest()
   234          batch_request_message.ParseFromString(message_content)
   235          if batch_request_message.nonce in self._seen_requests:
   236              LOGGER.debug("Received repeat GossipBatchByTransactionIdRequest"
   237                           " from %s", connection_id)
   238  
   239              return HandlerResult(HandlerStatus.DROP)
   240  
   241          batch = None
   242          batches = []
   243          unfound_txn_ids = []
   244          not_requested = []
   245          for txn_id in batch_request_message.ids:
   246              batch = self._responder.check_for_batch_by_transaction(
   247                  txn_id)
   248  
   249              # The txn_id was not found.
   250              if batch is None:
   251                  unfound_txn_ids.append(txn_id)
   252                  if not self._responder.already_requested(txn_id):
   253                      not_requested.append(txn_id)
   254                  else:
   255                      LOGGER.debug("Batch containing Transaction %s has already "
   256                                   "been requested", txn_id)
   257  
   258              # Check to see if a previous txn was in the same batch.
   259              elif batch not in batches:
   260                  batches.append(batch)
   261  
   262              batch = None
   263  
   264          if batches == [] and len(not_requested) == \
   265                  len(batch_request_message.ids):
   266  
   267              if batch_request_message.time_to_live > 0:
   268                  time_to_live = batch_request_message.time_to_live
   269                  batch_request_message.time_to_live = time_to_live - 1
   270                  self._gossip.broadcast(
   271                      batch_request_message,
   272                      validator_pb2.Message.
   273                      GOSSIP_BATCH_BY_TRANSACTION_ID_REQUEST,
   274                      exclude=[connection_id])
   275  
   276                  self._seen_requests[batch_request_message.nonce] = \
   277                      batch_request_message.ids
   278  
   279                  for txn_id in batch_request_message.ids:
   280                      self._responder.add_request(txn_id, connection_id)
   281  
   282          elif unfound_txn_ids != []:
   283              if not_requested != []:
   284                  if batch_request_message.time_to_live > 0:
   285                      self._seen_requests[batch_request_message.nonce] = \
   286                          batch_request_message.ids
   287                      new_request = \
   288                          network_pb2.GossipBatchByTransactionIdRequest()
   289                      # only request batches we have not requested already
   290                      new_request.ids.extend(not_requested)
   291                      # Keep same nonce as original message
   292                      new_request.nonce = batch_request_message.nonce
   293                      time_to_live = batch_request_message.time_to_live
   294                      new_request.time_to_live = time_to_live - 1
   295  
   296                      self._gossip.broadcast(
   297                          new_request,
   298                          validator_pb2.Message.
   299                          GOSSIP_BATCH_BY_TRANSACTION_ID_REQUEST,
   300                          exclude=[connection_id])
   301                      # Add all requests to responder
   302                      for txn_id in unfound_txn_ids:
   303                          self._responder.add_request(txn_id, connection_id)
   304              else:
   305                  # Add all requests to responder
   306                  for txn_id in unfound_txn_ids:
   307                      self._responder.add_request(txn_id, connection_id)
   308  
   309          if batches != []:
   310              for batch in batches:
   311                  LOGGER.debug("Responding to batch requests %s",
   312                               batch.header_signature)
   313  
   314                  batch_response = network_pb2.GossipBatchResponse(
   315                      content=batch.SerializeToString(),
   316                  )
   317  
   318                  self._gossip.send(validator_pb2.Message.GOSSIP_BATCH_RESPONSE,
   319                                    batch_response.SerializeToString(),
   320                                    connection_id)
   321  
   322          return HandlerResult(HandlerStatus.PASS)
   323  
   324  
   325  class ResponderBatchResponseHandler(Handler):
   326      def __init__(self, responder, gossip):
   327          self._responder = responder
   328          self._gossip = gossip
   329  
   330      def handle(self, connection_id, message_content):
   331          batch, message_content = message_content
   332  
   333          open_request = self._responder.get_request(batch.header_signature)
   334  
   335          if open_request is None:
   336              open_request = []
   337  
   338          requests_to_remove = [batch.header_signature]
   339          for txn in batch.transactions:
   340              requests_by_txn = self._responder.get_request(txn.header_signature)
   341              if requests_by_txn is not None:
   342                  open_request += requests_by_txn
   343                  requests_to_remove += [txn.header_signature]
   344  
   345          for connection in open_request:
   346              LOGGER.debug("Responding to batch requests: Send %s to %s",
   347                           batch.header_signature,
   348                           connection)
   349              try:
   350                  self._gossip.send(validator_pb2.Message.GOSSIP_BATCH_RESPONSE,
   351                                    message_content,
   352                                    connection)
   353              except ValueError:
   354                  LOGGER.debug("Can't send batch response %s to closed "
   355                               "connection %s",
   356                               batch.header_signature,
   357                               connection)
   358  
   359          for requested_id in requests_to_remove:
   360              self._responder.remove_request(requested_id)
   361  
   362          return HandlerResult(HandlerStatus.PASS)