def test_batch_by_transaction_id_response_handler(self): """ Test that the BatchByTransactionIdResponderHandler correctly broadcasts a received request that the Responder cannot respond to, or sends a GossipBatchResponse back to the connection_id the handler received the request from. """ # The completer does not have the requested batch with the transaction message = network_pb2.GossipBatchByTransactionIdRequest( ids=["123"], node_id=b"1", nonce="1") self.batch_by_txn_request_handler.handle( "Connection_1", message.SerializeToString()) # If we cannot respond to the request, broadcast batch request and add # to pending request self.assert_message_was_broadcasted( message, validator_pb2.Message.GOSSIP_BATCH_BY_TRANSACTION_ID_REQUEST ) self.assert_request_pending( requested_id="123", connection_id="Connection_1") self.assert_message_not_sent(connection_id="Connection_1") # Add the batch to the completer and resend the # BatchByTransactionIdRequest message = network_pb2.GossipBatchByTransactionIdRequest( ids=["123"], node_id=b"1", nonce="2") transaction = transaction_pb2.Transaction(header_signature="123") batch = batch_pb2.Batch( header_signature="abc", transactions=[transaction]) self.completer.add_batch(batch) self.batch_request_handler.handle( "Connection_1", message.SerializeToString()) # Check that the a Batch Response was sent back to "Connection_1" self.assert_message_sent( connection_id="Connection_1", message_type=validator_pb2.Message.GOSSIP_BATCH_RESPONSE )
def _create_batch(signer, transactions): """Creates a batch from a list of transactions and a signer, and signs the resulting batch with the given signing key. Args: signer (:obj:`Signer`): Cryptographic signer to sign the batch transactions (list of `Transaction`): The transactions to add to the batch. Returns: `Batch`: The constructed and signed batch. """ txn_ids = [txn.header_signature for txn in transactions] batch_header = batch_pb.BatchHeader( signer_public_key=signer.get_public_key().as_hex(), transaction_ids=txn_ids).SerializeToString() return batch_pb.Batch(header=batch_header, header_signature=signer.sign(batch_header), transactions=transactions)
def test_responder_batch_response_txn_handler(self): """ Test that the ResponderBatchResponseHandler, after receiving a Batch Response, checks to see if the responder has any pending request for that transactions in the batch and forwards the response on to the connection_id that had them. """ transaction = transaction_pb2.Transaction(header_signature="123") batch = batch_pb2.Batch(header_signature="abc", transactions=[transaction]) response_message = network_pb2.GossipBatchResponse( content=batch.SerializeToString()) request_message = \ network_pb2.GossipBatchByTransactionIdRequest( ids=["123"], time_to_live=1) # Send BatchByTransaciontIdRequest for txn "123" and add it to the # pending request cache self.batch_request_handler.handle("Connection_2", request_message.SerializeToString()) self.assert_request_pending(requested_id="123", connection_id="Connection_2") # Send Batch Response that contains the batch that has txn "123" self.batch_response_handler.handle( "Connection_1", response_message.SerializeToString()) # Handle the the BatchResponse Message. Since Connection_2 had # requested the txn_id in the batch but it could not be fulfilled at # that time of the request the received BatchResponse is forwarded to # Connection_2 self.assert_message_sent( connection_id="Connection_2", message_type=validator_pb2.Message.GOSSIP_BATCH_RESPONSE) # The request for transaction_id "123" from "Connection_2" is no # longer pending it should be removed from the pending request cache. self.assert_request_not_pending(requested_id="123")
def _create_batch(pubkey, signing_key, transactions): """Creates a batch from a list of transactions and a public key, and signs the resulting batch with the given signing key. Args: pubkey (str): The public key associated with the signing key. signing_key (str): The private key for signing the batch. transactions (list of `Transaction`): The transactions to add to the batch. Returns: `Batch`: The constructed and signed batch. """ txn_ids = [txn.header_signature for txn in transactions] batch_header = batch_pb.BatchHeader( signer_pubkey=pubkey, transaction_ids=txn_ids).SerializeToString() return batch_pb.Batch(header=batch_header, header_signature=signing.sign( batch_header, signing_key), transactions=transactions)
def test_batch_by_id_responder_handler(self): """ Test that the BatchByBatchIdResponderHandler correctly broadcasts a received request that the Responder cannot respond to, or sends a GossipBatchResponse back to the connection_id the handler received the request from. """ # The completer does not have the requested batch before_message = network_pb2.GossipBatchByBatchIdRequest( id="abc", nonce="1", time_to_live=1) after_message = network_pb2.GossipBatchByBatchIdRequest(id="abc", nonce="1", time_to_live=0) self.batch_request_handler.handle("Connection_1", before_message.SerializeToString()) # If we cannot respond to the request broadcast batch request and add # to pending request self.assert_message_was_broadcasted( after_message, validator_pb2.Message.GOSSIP_BATCH_BY_BATCH_ID_REQUEST) self.assert_request_pending(requested_id="abc", connection_id="Connection_1") self.assert_message_not_sent(connection_id="Connection_1") # Add the batch to the completer and resend the BatchByBatchIdRequest message = network_pb2.GossipBatchByBatchIdRequest(id="abc", nonce="2", time_to_live=1) batch = batch_pb2.Batch(header_signature="abc") self.completer.add_batch(batch) self.batch_request_handler.handle("Connection_1", message.SerializeToString()) # Check that the a Batch Response was sent back to "Connection_1" self.assert_message_sent( connection_id="Connection_1", message_type=validator_pb2.Message.GOSSIP_BATCH_RESPONSE)
def test_batch_by_transaction_id_multiple_txn_ids(self): """ Test that the BatchByTransactionIdResponderHandler correctly broadcasts a new request with only the transaction_ids that the Responder cannot respond to, and sends a GossipBatchResponse for the transactions_id requests that can be satisfied. """ # Add batch that has txn 123 transaction = transaction_pb2.Transaction(header_signature="123") batch = batch_pb2.Batch(header_signature="abc", transactions=[transaction]) self.completer.add_batch(batch) # Request transactions 123 and 456 message = network_pb2.GossipBatchByTransactionIdRequest( ids=["123", "456"], time_to_live=1) self.batch_by_txn_request_handler.handle("Connection_1", message.SerializeToString()) self.batch_request_handler.handle("Connection_1", message.SerializeToString()) # Respond with a BatchResponse for transaction 123 self.assert_message_sent( connection_id="Connection_1", message_type=validator_pb2.Message.GOSSIP_BATCH_RESPONSE) # Broadcast a BatchByTransactionIdRequest for just 456 after_message = \ network_pb2.GossipBatchByTransactionIdRequest( ids=["456"], time_to_live=0) self.assert_message_was_broadcasted( after_message, validator_pb2.Message.GOSSIP_BATCH_BY_TRANSACTION_ID_REQUEST) # And set a pending request for 456 self.assert_request_pending(requested_id="456", connection_id="Connection_1")
def do_responder_batch_response_handler(): batch = batch_pb2.Batch(header_signature="abc") response_message = network_pb2.GossipBatchResponse( content=batch.SerializeToString()) testResponder.batch_response_handler.handle( "Connection_1", (batch, response_message.SerializeToString())) # ResponderBlockResponseHandler should not send any messages. testResponder.assert_message_not_sent("Connection_1") testResponder.assert_request_not_pending(requested_id="abc") # Handle a request message for batch "abc". This adds it to the pending # request queue. request_message = \ network_pb2.GossipBatchByBatchIdRequest(id="abc", time_to_live=1) testResponder.batch_request_handler.handle( "Connection_2", request_message.SerializeToString()) testResponder.assert_request_pending(requested_id="abc", connection_id="Connection_2") # Handle the the BatchResponse Message. Since Connection_2 had # requested the batch but it could not be fulfilled at that time of the # request the received BatchResponse is forwarded to Connection_2 testResponder.batch_response_handler.handle( "Connection_1", (batch, response_message.SerializeToString())) testResponder.assert_message_sent( connection_id="Connection_2", message_type=validator_pb2.Message.GOSSIP_BATCH_RESPONSE) # The request for batch "abc" from "Connection_2" is no longer pending # it should be removed from the pending request cache. testResponder.assert_request_not_pending(requested_id="abc")
def handle(self, connection_id, message_content): batch_response = network_pb2.GossipBatchResponse() batch_response.ParseFromString(message_content) batch = batch_pb2.Batch() batch.ParseFromString(batch_response.content) open_request = self._responder.get_request(batch.header_signature) if open_request is None: open_request = [] requests_to_remove = [batch.header_signature] for txn in batch.transactions: requests_by_txn = self._responder.get_request(txn.header_signature) if requests_by_txn is not None: open_request += requests_by_txn requests_to_remove += [txn.header_signature] for connection in open_request: LOGGER.debug("Responding to batch requests: Send %s to %s", batch.header_signature, connection) try: self._gossip.send(validator_pb2.Message.GOSSIP_BATCH_RESPONSE, message_content, connection) except ValueError: LOGGER.debug( "Can't send batch response %s to closed " "connection %s", batch.header_signature, connection) for requested_id in requests_to_remove: self._responder.remove_request(requested_id) ack = network_pb2.NetworkAcknowledgement() ack.status = ack.OK return HandlerResult(HandlerStatus.RETURN, message_out=ack, message_type=validator_pb2.Message.NETWORK_ACK)
def make_batch(batch_id, txn_id): transaction = transaction_pb2.Transaction(header_signature=txn_id) batch = batch_pb2.Batch(header_signature=batch_id, transactions=[transaction]) return batch