def create_transaction(payload, private_key, public_key, inputs=None, outputs=None, dependencies=None): addr = '000000' + hashlib.sha512(payload).hexdigest()[:64] if inputs is None: inputs = [addr] if outputs is None: outputs = [addr] if dependencies is None: dependencies = [] header = transaction_pb2.TransactionHeader( signer_pubkey=public_key, family_name='scheduler_test', family_version='1.0', inputs=inputs, outputs=outputs, dependencies=dependencies, nonce=str(time.time()), payload_encoding="application/cbor", payload_sha512=hashlib.sha512(payload).hexdigest(), batcher_pubkey=public_key) header_bytes = header.SerializeToString() signature = signing.sign(header_bytes, private_key) transaction = transaction_pb2.Transaction( header=header_bytes, payload=payload, header_signature=signature) return transaction, header
def do_batch_by_transaction_id_multiple_txn_ids(): transaction = transaction_pb2.Transaction(header_signature="123") batch = batch_pb2.Batch(header_signature="abc", transactions=[transaction]) testResponder.completer.add_batch(batch) # Request transactions 123 and 456 message = network_pb2.GossipBatchByTransactionIdRequest( ids=["123", "456"], time_to_live=1) testResponder.batch_by_txn_request_handler.handle( "Connection_1", message.SerializeToString()) testResponder.batch_request_handler.handle("Connection_1", message.SerializeToString()) # Respond with a BatchResponse for transaction 123 testResponder.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) testResponder.assert_message_was_broadcasted( after_message, validator_pb2.Message.GOSSIP_BATCH_BY_TRANSACTION_ID_REQUEST) # And set a pending request for 456 testResponder.assert_request_pending(requested_id="456", connection_id="Connection_1")
def create_transaction(name, private_key, public_key): payload = name addr = '000000' + hashlib.sha512(name.encode()).hexdigest() header = transaction_pb2.TransactionHeader( signer_pubkey=public_key, family_name='scheduler_test', family_version='1.0', inputs=[addr], outputs=[addr], dependencies=[], payload_encoding="application/cbor", payload_sha512=hashlib.sha512(payload.encode()).hexdigest(), batcher_pubkey=public_key) header_bytes = header.SerializeToString() signature = bitcoin.ecdsa_sign( header_bytes, private_key) transaction = transaction_pb2.Transaction( header=header_bytes, payload=payload.encode(), header_signature=signature) return transaction
def do_responder_batch_response_txn_handler(): 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 testResponder.batch_request_handler.handle( "Connection_2", request_message.SerializeToString()) testResponder.assert_request_pending(requested_id="123", connection_id="Connection_2") # Send Batch Response that contains the batch that has txn "123" testResponder.batch_response_handler.handle( "Connection_1", (batch, 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 testResponder.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. testResponder.assert_request_not_pending(requested_id="123")
def do_batch_by_transaction_id_response_handler(): before_message = network_pb2.GossipBatchByTransactionIdRequest( ids=["123"], nonce="1", time_to_live=1) after_message = network_pb2.GossipBatchByTransactionIdRequest( ids=["123"], nonce="1", time_to_live=0) testResponder.batch_by_txn_request_handler.handle( "Connection_1", before_message.SerializeToString()) # If we cannot respond to the request, broadcast batch request and add # to pending request testResponder.assert_message_was_broadcasted( after_message, validator_pb2.Message.GOSSIP_BATCH_BY_TRANSACTION_ID_REQUEST) testResponder.assert_request_pending(requested_id="123", connection_id="Connection_1") testResponder.assert_message_not_sent(connection_id="Connection_1") # Add the batch to the completer and resend the # BatchByTransactionIdRequest message = network_pb2.GossipBatchByTransactionIdRequest(ids=["123"], nonce="2", time_to_live=1) transaction = transaction_pb2.Transaction(header_signature="123") batch = batch_pb2.Batch(header_signature="abc", transactions=[transaction]) testResponder.completer.add_batch(batch) testResponder.batch_request_handler.handle("Connection_1", message.SerializeToString()) # Check that the a Batch Response was sent back to "Connection_1" testResponder.assert_message_sent( connection_id="Connection_1", message_type=validator_pb2.Message.GOSSIP_BATCH_RESPONSE)
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") 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 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 test_responder_batch_response_txn_handler(): """ 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. """ testResponder = TestResponder() 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 testResponder.batch_request_handler.handle( "Connection_2", request_message.SerializeToString()) # Send Batch Response that contains the batch that has txn "123" testResponder.batch_response_handler.handle( "Connection_1", (batch, response_message.SerializeToString()))
def test_batch_by_transaction_id_multiple_txn_ids(): """ 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 testResponder = TestResponder() transaction = transaction_pb2.Transaction(header_signature="123") batch = batch_pb2.Batch(header_signature="abc", transactions=[transaction]) testResponder.completer.add_batch(batch) # Request transactions 123 and 456 message = network_pb2.GossipBatchByTransactionIdRequest(ids=["123", "456"], time_to_live=1) testResponder.batch_by_txn_request_handler.handle( "Connection_1", message.SerializeToString()) testResponder.batch_request_handler.handle("Connection_1", message.SerializeToString()) # Respond with a BatchResponse for transaction 123 # Broadcast a BatchByTransactionIdRequest for just 456 after_message = \ network_pb2.GossipBatchByTransactionIdRequest( ids=["456"], time_to_live=0)
def test_batch_by_transaction_id_response_handler(): """ 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 testResponder = TestResponder() before_message = network_pb2.GossipBatchByTransactionIdRequest( ids=["123"], nonce="1", time_to_live=1) after_message = network_pb2.GossipBatchByTransactionIdRequest( ids=["123"], nonce="1", time_to_live=0) testResponder.batch_by_txn_request_handler.handle( "Connection_1", before_message.SerializeToString()) # If we cannot respond to the request, broadcast batch request and add # to pending request # BatchByTransactionIdRequest message = network_pb2.GossipBatchByTransactionIdRequest(ids=["123"], nonce="2", time_to_live=1) transaction = transaction_pb2.Transaction(header_signature="123") batch = batch_pb2.Batch(header_signature="abc", transactions=[transaction]) testResponder.completer.add_batch(batch) testResponder.batch_request_handler.handle("Connection_1", message.SerializeToString())
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"]) # 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 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 _register_signup_information(self, block_header, poet_enclave_module): # Create signup information for this validator, putting the block ID # of the block previous to the block referenced by block_header in the # nonce. Block ID is better than wait certificate ID for testing # freshness as we need to account for non-PoET blocks. public_key_hash = \ hashlib.sha256( block_header.signer_pubkey.encode()).hexdigest() nonce = SignupInfo.block_id_to_nonce(block_header.previous_block_id) signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=poet_enclave_module, originator_public_key_hash=public_key_hash, nonce=nonce) # Create the validator registry payload payload = \ vr_pb.ValidatorRegistryPayload( verb='register', name='validator-{}'.format(block_header.signer_pubkey[:8]), id=block_header.signer_pubkey, signup_info=vr_pb.SignUpInfo( poet_public_key=signup_info.poet_public_key, proof_data=signup_info.proof_data, anti_sybil_id=signup_info.anti_sybil_id, nonce=nonce), ) serialized = payload.SerializeToString() # Create the address that will be used to look up this validator # registry transaction. Seems like a potential for refactoring.. validator_entry_address = \ PoetBlockPublisher._validator_registry_namespace + \ hashlib.sha256(block_header.signer_pubkey.encode()).hexdigest() # Create a transaction header and transaction for the validator # registry update amd then hand it off to the batch publisher to # send out. output_addresses = \ [validator_entry_address, PoetBlockPublisher._validator_map_address] input_addresses = \ output_addresses + \ [SettingsView.setting_address( 'sawtooth.poet.report_public_key_pem'), SettingsView.setting_address( 'sawtooth.poet.valid_enclave_measurements'), SettingsView.setting_address( 'sawtooth.poet.valid_enclave_basenames')] header = \ txn_pb.TransactionHeader( signer_pubkey=block_header.signer_pubkey, family_name='sawtooth_validator_registry', family_version='1.0', inputs=input_addresses, outputs=output_addresses, dependencies=[], payload_sha512=hashlib.sha512(serialized).hexdigest(), batcher_pubkey=block_header.signer_pubkey, nonce=time.time().hex().encode()).SerializeToString() signature = \ signing.sign(header, self._batch_publisher.identity_signing_key) transaction = \ txn_pb.Transaction( header=header, payload=serialized, header_signature=signature) LOGGER.info( 'Register Validator Name=%s, ID=%s...%s, PoET public key=%s...%s, ' 'Nonce=%s', payload.name, payload.id[:8], payload.id[-8:], payload.signup_info.poet_public_key[:8], payload.signup_info.poet_public_key[-8:], nonce) self._batch_publisher.send([transaction]) # Store the key state so that we can look it up later if need be and # set the new key as our active key LOGGER.info( 'Save key state PPK=%s...%s => SSD=%s...%s', signup_info.poet_public_key[:8], signup_info.poet_public_key[-8:], signup_info.sealed_signup_data[:8], signup_info.sealed_signup_data[-8:]) self._poet_key_state_store[signup_info.poet_public_key] = \ PoetKeyState( sealed_signup_data=signup_info.sealed_signup_data, has_been_refreshed=False) self._poet_key_state_store.active_key = signup_info.poet_public_key
def do_create(args): """Executes the `poet registration` subcommand. This command generates a validator registry transaction and saves it to a file, whose location is determined by the args. The signup data, generated by the selected enclave, is also stored in a well-known location. """ signer = _read_signer(args.key) public_key = signer.get_public_key().as_hex() public_key_hash = sha256(public_key.encode()).hexdigest() with PoetEnclaveModuleWrapper( enclave_module=args.enclave_module, config_dir=config.get_config_dir(), data_dir=config.get_data_dir()) as poet_enclave_module: signup_info = SignupInfo.create_signup_info( poet_enclave_module=poet_enclave_module, originator_public_key_hash=public_key_hash, nonce=SignupInfo.block_id_to_nonce(args.block)) print('Writing key state for PoET public key: {}...{}'.format( signup_info.poet_public_key[:8], signup_info.poet_public_key[-8:])) # Store the newly-created PoET key state, associating it with its # corresponding public key poet_key_state_store = \ PoetKeyStateStore( data_dir=config.get_data_dir(), validator_id=public_key) poet_key_state_store[signup_info.poet_public_key] = \ PoetKeyState( sealed_signup_data=signup_info.sealed_signup_data, has_been_refreshed=False) # Create the validator registry payload payload = \ vr_pb.ValidatorRegistryPayload( verb='register', name='validator-{}'.format(public_key[:8]), id=public_key, signup_info=vr_pb.SignUpInfo( poet_public_key=signup_info.poet_public_key, proof_data=signup_info.proof_data, anti_sybil_id=signup_info.anti_sybil_id, nonce=SignupInfo.block_id_to_nonce(args.block))) serialized = payload.SerializeToString() # Create the address that will be used to look up this validator # registry transaction. Seems like a potential for refactoring.. validator_entry_address = \ VR_NAMESPACE + sha256(public_key.encode()).hexdigest() # Create a transaction header and transaction for the validator # registry update amd then hand it off to the batch publisher to # send out. output_addresses = [validator_entry_address, VALIDATOR_MAP_ADDRESS] input_addresses = \ output_addresses + \ [SettingsView.setting_address('sawtooth.poet.report_public_key_pem'), SettingsView.setting_address('sawtooth.poet.' 'valid_enclave_measurements'), SettingsView.setting_address('sawtooth.poet.valid_enclave_basenames')] header = \ txn_pb.TransactionHeader( signer_public_key=public_key, family_name='sawtooth_validator_registry', family_version='1.0', inputs=input_addresses, outputs=output_addresses, dependencies=[], payload_sha512=sha512(serialized).hexdigest(), batcher_public_key=public_key, nonce=time.time().hex().encode()).SerializeToString() signature = signer.sign(header) transaction = \ txn_pb.Transaction( header=header, payload=serialized, header_signature=signature) batch = _create_batch(signer, [transaction]) batch_list = batch_pb.BatchList(batches=[batch]) try: print('Generating {}'.format(args.output)) with open(args.output, 'wb') as batch_file: batch_file.write(batch_list.SerializeToString()) except IOError as e: raise CliException('Unable to write to batch file: {}'.format(str(e)))
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
def do_genesis(args): """Executes the `poet genesis` subcommand. This command generates a validator registry transaction and saves it to a file, whose location is determined by the args. The signup data, generated by the selected enclave, is also stored in a well-known location. """ if args.enclave_module == 'simulator': module_name = SIMUATOR_MODULE elif args.enclave_module == 'sgx': module_name = SGX_MODULE else: raise AssertionError('Unknown enclave module: {}'.format( args.enclave_module)) try: poet_enclave_module = importlib.import_module(module_name) except ImportError as e: raise AssertionError(str(e)) poet_enclave_module.initialize(**{}) pubkey, signing_key = _read_signing_keys(args.key) public_key_hash = sha256(pubkey.encode()).hexdigest() signup_info = SignupInfo.create_signup_info( poet_enclave_module=poet_enclave_module, validator_address=pubkey, originator_public_key_hash=public_key_hash, nonce=NULL_BLOCK_IDENTIFIER) print('Writing key state for PoET public key: {}...{}'.format( signup_info.poet_public_key[:8], signup_info.poet_public_key[-8:])) # Store the newly-created PoET key state, associating it with its # corresponding public key poet_key_state_store = \ PoetKeyStateStore( data_dir=config.get_data_dir(), validator_id=pubkey) poet_key_state_store[signup_info.poet_public_key] = \ PoetKeyState( sealed_signup_data=signup_info.sealed_signup_data, has_been_refreshed=False) # Create the validator registry payload payload = \ vr_pb.ValidatorRegistryPayload( verb='register', name='validator-{}'.format(pubkey[:8]), id=pubkey, signup_info=vr_pb.SignUpInfo( poet_public_key=signup_info.poet_public_key, proof_data=signup_info.proof_data, anti_sybil_id=signup_info.anti_sybil_id, nonce=NULL_BLOCK_IDENTIFIER)) serialized = payload.SerializeToString() # Create the address that will be used to look up this validator # registry transaction. Seems like a potential for refactoring.. validator_entry_address = \ VR_NAMESPACE + sha256(pubkey.encode()).hexdigest() # Create a transaction header and transaction for the validator # registry update amd then hand it off to the batch publisher to # send out. addresses = [validator_entry_address, VALIDATOR_MAP_ADDRESS] header = \ txn_pb.TransactionHeader( signer_pubkey=pubkey, family_name='sawtooth_validator_registry', family_version='1.0', inputs=addresses, outputs=addresses, dependencies=[], payload_encoding="application/protobuf", payload_sha512=sha512(serialized).hexdigest(), batcher_pubkey=pubkey, nonce=time.time().hex().encode()).SerializeToString() signature = signing.sign(header, signing_key) transaction = \ txn_pb.Transaction( header=header, payload=serialized, header_signature=signature) batch = _create_batch(pubkey, signing_key, [transaction]) batch_list = batch_pb.BatchList(batches=[batch]) try: print('Generating {}'.format(args.output)) with open(args.output, 'wb') as batch_file: batch_file.write(batch_list.SerializeToString()) except IOError as e: raise CliException('Unable to write to batch file: {}'.format(str(e)))
def _register_signup_information(self, block_header): # Find the most-recent block in the block cache, if such a block # exists, and get its wait certificate ID wait_certificate_id = NULL_BLOCK_IDENTIFIER most_recent_block = self._block_cache.block_store.chain_head if most_recent_block is not None: wait_certificate_id = \ utils.deserialize_wait_certificate( block=most_recent_block, poet_enclave_module=self._poet_enclave_module).identifier # Create signup information for this validator public_key_hash = \ hashlib.sha256( block_header.signer_pubkey.encode()).hexdigest() signup_info = \ SignupInfo.create_signup_info( poet_enclave_module=self._poet_enclave_module, validator_address=block_header.signer_pubkey, originator_public_key_hash=public_key_hash, most_recent_wait_certificate_id=wait_certificate_id) # Create the validator registry payload payload = \ vr_pb.ValidatorRegistryPayload( verb='register', name='validator-{}'.format(block_header.signer_pubkey[-8:]), id=block_header.signer_pubkey, signup_info=vr_pb.SignUpInfo( poet_public_key=signup_info.poet_public_key, proof_data=signup_info.proof_data, anti_sybil_id=signup_info.anti_sybil_id), block_num=block_header.block_num) serialized = payload.SerializeToString() # Create the address that will be used to look up this validator # registry transaction. Seems like a potential for refactoring.. validator_entry_address = \ PoetBlockPublisher._validator_registry_namespace + \ hashlib.sha256(block_header.signer_pubkey.encode()).hexdigest() # Create a transaction header and transaction for the validator # registry update amd then hand it off to the batch publisher to # send out. addresses = \ [validator_entry_address, PoetBlockPublisher._validator_map_address] header = \ txn_pb.TransactionHeader( signer_pubkey=block_header.signer_pubkey, family_name='sawtooth_validator_registry', family_version='1.0', inputs=addresses, outputs=addresses, dependencies=[], payload_encoding="application/protobuf", payload_sha512=hashlib.sha512(serialized).hexdigest(), batcher_pubkey=block_header.signer_pubkey, nonce=time.time().hex().encode()).SerializeToString() signature = \ signing.sign(header, self._batch_publisher.identity_signing_key) transaction = \ txn_pb.Transaction( header=header, payload=serialized, header_signature=signature) self._batch_publisher.send([transaction]) LOGGER.info( 'Register Validator Name=%s, ID=%s...%s, PoET public key=%s...%s', payload.name, payload.id[:8], payload.id[-8:], payload.signup_info.poet_public_key[:8], payload.signup_info.poet_public_key[-8:]) # HACER: Once we have the consensus state implemented, we can # store this information in there. For now, we will store in the # class so it persists for the lifetime of the validator. PoetBlockPublisher._sealed_signup_data = \ signup_info.sealed_signup_data PoetBlockPublisher._poet_public_key = signup_info.poet_public_key