class ClientBatchSubmitBackpressureHandler(Handler): """This handler receives a batch list, and accepts it if the system is able. Otherwise it returns a QUEUE_FULL response. """ def __init__(self, can_accept_fn, queue_info_fn, metrics_registry=None): self._can_accept = can_accept_fn self._queue_info = queue_info_fn self._applying_backpressure = False if metrics_registry: self._batches_rejected_count = CounterWrapper( metrics_registry.counter( 'backpressure_batches_rejected_count')) self._batches_rejected_gauge = GaugeWrapper( metrics_registry.gauge( 'backpressure_batches_rejected_gauge', default=0)) else: self._batches_rejected_count = CounterWrapper() self._batches_rejected_gauge = GaugeWrapper() def handle(self, connection_id, message_content): if not self._can_accept(): if not self._applying_backpressure: self._applying_backpressure = True LOGGER.info( 'Applying back pressure on client submitted batches: ' 'current depth: %s, limit: %s', *self._queue_info()) self._batches_rejected_count.inc() self._batches_rejected_gauge.set_value( self._batches_rejected_gauge.get_value() + 1) response = ClientBatchSubmitResponse( status=ClientBatchSubmitResponse.QUEUE_FULL) return HandlerResult( status=HandlerStatus.RETURN, message_out=response, message_type=Message.CLIENT_BATCH_SUBMIT_RESPONSE ) else: if self._applying_backpressure: self._applying_backpressure = False self._batches_rejected_gauge.set_value(0) LOGGER.info( 'Ending back pressure on client submitted batches: ' 'current depth: %s, limit: %s', *self._queue_info()) return HandlerResult(status=HandlerStatus.PASS)
def __init__(self, can_accept_fn, queue_info_fn, metrics_registry=None): self._can_accept = can_accept_fn self._queue_info = queue_info_fn self._applying_backpressure = False if metrics_registry: self._batches_rejected_count = CounterWrapper( metrics_registry.counter( 'backpressure_batches_rejected_count')) self._batches_rejected_gauge = GaugeWrapper( metrics_registry.gauge( 'backpressure_batches_rejected_gauge', default=0)) else: self._batches_rejected_count = CounterWrapper() self._batches_rejected_gauge = GaugeWrapper()
def __init__(self, max_workers=None, name='', trace=None, metrics_registry=None): if trace is None: self._trace = 'SAWTOOTH_TRACE_LOGGING' in os.environ else: self._trace = trace self._name = name if name == '': self._name = 'Instrumented' LOGGER.debug('Creating thread pool executor %s', self._name) self._workers_in_use = atomic.Counter() self._max_workers = max_workers if self._max_workers is None: # This is the same default as ThreadPoolExecutor, but we want to # know how many workers there are for logging self._max_workers = multiprocessing.cpu_count() * 5 super().__init__(max_workers) if metrics_registry: # Tracks how many workers are already in use self._workers_already_in_use_gauge = GaugeWrapper( metrics_registry.gauge( '{}-threadpool.workers_already_in_use'.format(self._name))) # Tracks how long tasks take to run self._task_run_timer = TimerWrapper( metrics_registry.timer( '{}-threadpool.task_run_time'.format(self._name))) # Tracks how long tasks wait in the queue self._task_time_in_queue_timer = TimerWrapper( metrics_registry.timer( '{}-threadpool.task_time_in_queue'.format(self._name))) else: self._workers_already_in_use_gauge = GaugeWrapper() self._task_run_timer = TimerWrapper() self._task_time_in_queue_timer = TimerWrapper()
def __init__(self, block_cache, block_validator, state_view_factory, chain_head_lock, on_chain_updated, chain_id_manager, data_dir, config_dir, chain_observers, metrics_registry=None): """Initialize the ChainController Args: block_cache: The cache of all recent blocks and the processing state associated with them. block_validator: The object to use for submitting block validation work. state_view_factory: A factory that can be used to create read- only views of state for a particular merkle root, in particular the state as it existed when a particular block was the chain head. chain_head_lock: Lock to hold while the chain head is being updated, this prevents other components that depend on the chain head and the BlockStore from having the BlockStore change under them. This lock is only for core Journal components (BlockPublisher and ChainController), other components should handle block not found errors from the BlockStore explicitly. on_chain_updated: The callback to call to notify the rest of the system the head block in the chain has been changed. chain_id_manager: The ChainIdManager instance. data_dir: path to location where persistent data for the consensus module can be stored. config_dir: path to location where config data for the consensus module can be found. chain_observers (list of :obj:`ChainObserver`): A list of chain observers. metrics_registry: (Optional) Pyformance metrics registry handle for creating new metrics. Returns: None """ self._lock = RLock() self._chain_head_lock = chain_head_lock self._block_cache = block_cache self._block_store = block_cache.block_store self._state_view_factory = state_view_factory self._notify_on_chain_updated = on_chain_updated self._data_dir = data_dir self._config_dir = config_dir self._chain_id_manager = chain_id_manager self._chain_head = None self._chain_observers = chain_observers self._metrics_registry = metrics_registry if metrics_registry: self._chain_head_gauge = GaugeWrapper( metrics_registry.gauge('chain_head', default='no chain head')) self._committed_transactions_count = CounterWrapper( metrics_registry.counter('committed_transactions_count')) self._block_num_gauge = GaugeWrapper( metrics_registry.gauge('block_num')) self._blocks_considered_count = CounterWrapper( metrics_registry.counter('blocks_considered_count')) else: self._chain_head_gauge = GaugeWrapper() self._committed_transactions_count = CounterWrapper() self._block_num_gauge = GaugeWrapper() self._blocks_considered_count = CounterWrapper() self._block_queue = queue.Queue() self._chain_thread = None self._block_validator = block_validator # Only run this after all member variables have been bound self._set_chain_head_from_block_store()
class ChainController(object): """ To evaluating new blocks to determine if they should extend or replace the current chain. If they are valid extend the chain. """ def __init__(self, block_cache, block_validator, state_view_factory, chain_head_lock, on_chain_updated, chain_id_manager, data_dir, config_dir, chain_observers, metrics_registry=None): """Initialize the ChainController Args: block_cache: The cache of all recent blocks and the processing state associated with them. block_validator: The object to use for submitting block validation work. state_view_factory: A factory that can be used to create read- only views of state for a particular merkle root, in particular the state as it existed when a particular block was the chain head. chain_head_lock: Lock to hold while the chain head is being updated, this prevents other components that depend on the chain head and the BlockStore from having the BlockStore change under them. This lock is only for core Journal components (BlockPublisher and ChainController), other components should handle block not found errors from the BlockStore explicitly. on_chain_updated: The callback to call to notify the rest of the system the head block in the chain has been changed. chain_id_manager: The ChainIdManager instance. data_dir: path to location where persistent data for the consensus module can be stored. config_dir: path to location where config data for the consensus module can be found. chain_observers (list of :obj:`ChainObserver`): A list of chain observers. metrics_registry: (Optional) Pyformance metrics registry handle for creating new metrics. Returns: None """ self._lock = RLock() self._chain_head_lock = chain_head_lock self._block_cache = block_cache self._block_store = block_cache.block_store self._state_view_factory = state_view_factory self._notify_on_chain_updated = on_chain_updated self._data_dir = data_dir self._config_dir = config_dir self._chain_id_manager = chain_id_manager self._chain_head = None self._chain_observers = chain_observers self._metrics_registry = metrics_registry if metrics_registry: self._chain_head_gauge = GaugeWrapper( metrics_registry.gauge('chain_head', default='no chain head')) self._committed_transactions_count = CounterWrapper( metrics_registry.counter('committed_transactions_count')) self._block_num_gauge = GaugeWrapper( metrics_registry.gauge('block_num')) self._blocks_considered_count = CounterWrapper( metrics_registry.counter('blocks_considered_count')) else: self._chain_head_gauge = GaugeWrapper() self._committed_transactions_count = CounterWrapper() self._block_num_gauge = GaugeWrapper() self._blocks_considered_count = CounterWrapper() self._block_queue = queue.Queue() self._chain_thread = None self._block_validator = block_validator # Only run this after all member variables have been bound self._set_chain_head_from_block_store() def _set_chain_head_from_block_store(self): try: self._chain_head = self._block_store.chain_head if self._chain_head is not None: LOGGER.info("Chain controller initialized with chain head: %s", self._chain_head) self._chain_head_gauge.set_value( self._chain_head.identifier[:8]) except Exception: LOGGER.exception( "Invalid block store. Head of the block chain cannot be" " determined") raise def start(self): self._set_chain_head_from_block_store() self._notify_on_chain_updated(self._chain_head) self._chain_thread = _ChainThread( chain_controller=self, block_queue=self._block_queue, block_cache=self._block_cache) self._chain_thread.start() def stop(self): if self._chain_thread is not None: self._chain_thread.stop() self._chain_thread = None def queue_block(self, block): """ New block has been received, queue it with the chain controller for processing. """ self._block_queue.put(block) @property def chain_head(self): return self._chain_head def _submit_blocks_for_verification(self, blocks): self._block_validator.submit_blocks_for_verification( blocks, self.on_block_validated) def on_block_validated(self, commit_new_block, result): """Message back from the block validator, that the validation is complete Args: commit_new_block (Boolean): whether the new block should become the chain head or not. result (Dict): Map of the results of the fork resolution. Returns: None """ try: with self._lock: self._blocks_considered_count.inc() new_block = result.block # if the head has changed, since we started the work. if result.chain_head.identifier !=\ self._chain_head.identifier: LOGGER.info( 'Chain head updated from %s to %s while processing ' 'block: %s', result.chain_head, self._chain_head, new_block) LOGGER.debug('Verify block again: %s ', new_block) self._submit_blocks_for_verification([new_block]) # If the head is to be updated to the new block. elif commit_new_block: with self._chain_head_lock: self._chain_head = new_block # update the the block store to have the new chain self._block_store.update_chain(result.new_chain, result.current_chain) LOGGER.info( "Chain head updated to: %s", self._chain_head) self._chain_head_gauge.set_value( self._chain_head.identifier[:8]) self._committed_transactions_count.inc( result.transaction_count) self._block_num_gauge.set_value( self._chain_head.block_num) # tell the BlockPublisher else the chain is updated self._notify_on_chain_updated( self._chain_head, result.committed_batches, result.uncommitted_batches) for batch in new_block.batches: if batch.trace: LOGGER.debug("TRACE %s: %s", batch.header_signature, self.__class__.__name__) for block in reversed(result.new_chain): receipts = self._make_receipts(block.execution_results) # Update all chain observers for observer in self._chain_observers: observer.chain_update(block, receipts) # The block is otherwise valid, but we have determined we # don't want it as the chain head. else: LOGGER.info('Rejected new chain head: %s', new_block) # pylint: disable=broad-except except Exception: LOGGER.exception( "Unhandled exception in ChainController.on_block_validated()") def on_block_received(self, block): try: with self._lock: if self.has_block(block.header_signature): # do we already have this block return if self.chain_head is None: self._set_genesis(block) return self._block_cache[block.identifier] = block # schedule this block for validation. self._submit_blocks_for_verification([block]) # pylint: disable=broad-except except Exception: LOGGER.exception( "Unhandled exception in ChainController.on_block_received()") def has_block(self, block_id): with self._lock: if block_id in self._block_cache: return True if self._block_validator.in_process(block_id): return True if self._block_validator.in_pending(block_id): return True return False def _set_genesis(self, block): # This is used by a non-genesis journal when it has received the # genesis block from the genesis validator if block.previous_block_id == NULL_BLOCK_IDENTIFIER: chain_id = self._chain_id_manager.get_block_chain_id() if chain_id is not None and chain_id != block.identifier: LOGGER.warning("Block id does not match block chain id %s. " "Cannot set initial chain head.: %s", chain_id[:8], block.identifier[:8]) else: try: self._block_validator.validate_block(block) except BlockValidationError as err: LOGGER.warning( 'Cannot set chain head; ' 'genesis block %s is not valid: %s', block, err) return if chain_id is None: self._chain_id_manager.save_block_chain_id( block.identifier) self._block_store.update_chain([block]) self._chain_head = block self._notify_on_chain_updated(self._chain_head) else: LOGGER.warning("Cannot set initial chain head, this is not a " "genesis block: %s", block) def _make_receipts(self, results): receipts = [] for result in results: receipt = TransactionReceipt() receipt.data.extend([data for data in result.data]) receipt.state_changes.extend(result.state_changes) receipt.events.extend(result.events) receipt.transaction_id = result.signature receipts.append(receipt) return receipts
def __init__(self, transaction_executor, block_cache, state_view_factory, settings_cache, block_sender, batch_sender, squash_handler, chain_head, identity_signer, data_dir, config_dir, permission_verifier, check_publish_block_frequency, batch_observers, batch_injector_factory=None, metrics_registry=None): """ Initialize the BlockPublisher object Args: transaction_executor (:obj:`TransactionExecutor`): A TransactionExecutor instance. block_cache (:obj:`BlockCache`): A BlockCache instance. state_view_factory (:obj:`StateViewFactory`): StateViewFactory for read-only state views. block_sender (:obj:`BlockSender`): The BlockSender instance. batch_sender (:obj:`BatchSender`): The BatchSender instance. squash_handler (function): Squash handler function for merging contexts. chain_head (:obj:`BlockWrapper`): The initial chain head. identity_signer (:obj:`Signer`): Cryptographic signer for signing blocks data_dir (str): path to location where persistent data for the consensus module can be stored. config_dir (str): path to location where configuration can be found. batch_injector_factory (:obj:`BatchInjectorFatctory`): A factory for creating BatchInjectors. metrics_registry (MetricsRegistry): Metrics registry used to create pending batch gauge """ self._lock = RLock() self._candidate_block = None # _CandidateBlock helper, # the next block in potential chain self._block_cache = block_cache self._state_view_factory = state_view_factory self._settings_cache = settings_cache self._transaction_executor = transaction_executor self._block_sender = block_sender self._batch_publisher = BatchPublisher(identity_signer, batch_sender) self._pending_batches = [] # batches we are waiting for validation, # arranged in the order of batches received. self._pending_batch_ids = [] self._publish_count_average = _RollingAverage( NUM_PUBLISH_COUNT_SAMPLES, INITIAL_PUBLISH_COUNT) self._chain_head = chain_head # block (BlockWrapper) self._squash_handler = squash_handler self._identity_signer = identity_signer self._data_dir = data_dir self._config_dir = config_dir self._permission_verifier = permission_verifier self._batch_injector_factory = batch_injector_factory # For metric gathering if metrics_registry: self._pending_batch_gauge = GaugeWrapper( metrics_registry.gauge('pending_batch_gauge')) self._blocks_published_count = CounterWrapper( metrics_registry.counter('blocks_published_count')) else: self._blocks_published_count = CounterWrapper() self._pending_batch_gauge = GaugeWrapper() self._batch_queue = queue.Queue() self._queued_batch_ids = [] self._batch_observers = batch_observers self._check_publish_block_frequency = check_publish_block_frequency self._publisher_thread = None # A series of states that allow us to check for condition changes. # These can be used to log only at the boundary of condition changes. self._logging_states = _PublisherLoggingStates()
class BlockPublisher(object): """ Responsible for generating new blocks and publishing them when the Consensus deems it appropriate. """ def __init__(self, transaction_executor, block_cache, state_view_factory, settings_cache, block_sender, batch_sender, squash_handler, chain_head, identity_signer, data_dir, config_dir, permission_verifier, check_publish_block_frequency, batch_observers, batch_injector_factory=None, metrics_registry=None): """ Initialize the BlockPublisher object Args: transaction_executor (:obj:`TransactionExecutor`): A TransactionExecutor instance. block_cache (:obj:`BlockCache`): A BlockCache instance. state_view_factory (:obj:`StateViewFactory`): StateViewFactory for read-only state views. block_sender (:obj:`BlockSender`): The BlockSender instance. batch_sender (:obj:`BatchSender`): The BatchSender instance. squash_handler (function): Squash handler function for merging contexts. chain_head (:obj:`BlockWrapper`): The initial chain head. identity_signer (:obj:`Signer`): Cryptographic signer for signing blocks data_dir (str): path to location where persistent data for the consensus module can be stored. config_dir (str): path to location where configuration can be found. batch_injector_factory (:obj:`BatchInjectorFatctory`): A factory for creating BatchInjectors. metrics_registry (MetricsRegistry): Metrics registry used to create pending batch gauge """ self._lock = RLock() self._candidate_block = None # _CandidateBlock helper, # the next block in potential chain self._block_cache = block_cache self._state_view_factory = state_view_factory self._settings_cache = settings_cache self._transaction_executor = transaction_executor self._block_sender = block_sender self._batch_publisher = BatchPublisher(identity_signer, batch_sender) self._pending_batches = [] # batches we are waiting for validation, # arranged in the order of batches received. self._pending_batch_ids = [] self._publish_count_average = _RollingAverage( NUM_PUBLISH_COUNT_SAMPLES, INITIAL_PUBLISH_COUNT) self._chain_head = chain_head # block (BlockWrapper) self._squash_handler = squash_handler self._identity_signer = identity_signer self._data_dir = data_dir self._config_dir = config_dir self._permission_verifier = permission_verifier self._batch_injector_factory = batch_injector_factory # For metric gathering if metrics_registry: self._pending_batch_gauge = GaugeWrapper( metrics_registry.gauge('pending_batch_gauge')) self._blocks_published_count = CounterWrapper( metrics_registry.counter('blocks_published_count')) else: self._blocks_published_count = CounterWrapper() self._pending_batch_gauge = GaugeWrapper() self._batch_queue = queue.Queue() self._queued_batch_ids = [] self._batch_observers = batch_observers self._check_publish_block_frequency = check_publish_block_frequency self._publisher_thread = None # A series of states that allow us to check for condition changes. # These can be used to log only at the boundary of condition changes. self._logging_states = _PublisherLoggingStates() def start(self): self._publisher_thread = _PublisherThread( block_publisher=self, batch_queue=self._batch_queue, check_publish_block_frequency=self._check_publish_block_frequency) self._publisher_thread.start() def stop(self): if self._publisher_thread is not None: self._publisher_thread.stop() self._publisher_thread = None def queue_batch(self, batch): """ New batch has been received, queue it with the BlockPublisher for inclusion in the next block. """ self._batch_queue.put(batch) self._queued_batch_ids.append(batch.header_signature) for observer in self._batch_observers: observer.notify_batch_pending(batch) def can_accept_batch(self): return len(self._pending_batches) < self._get_current_queue_limit() def _get_current_queue_limit(self): # Limit the number of batches to 2 times the publishing average. This # allows the queue to grow geometrically, if the queue is drained. return 2 * self._publish_count_average.value def get_current_queue_info(self): """Returns a tuple of the current size of the pending batch queue and the current queue limit. """ return (len(self._pending_batches), self._get_current_queue_limit()) @property def chain_head_lock(self): return self._lock def _build_candidate_block(self, chain_head): """ Build a candidate block and construct the consensus object to validate it. :param chain_head: The block to build on top of. :return: (BlockBuilder) - The candidate block in a BlockBuilder wrapper. """ state_view = BlockWrapper.state_view_for_block( chain_head, self._state_view_factory) consensus_module = ConsensusFactory.get_configured_consensus_module( chain_head.header_signature, state_view) # using chain_head so so we can use the setting_cache max_batches = int(self._settings_cache.get_setting( 'sawtooth.publisher.max_batches_per_block', chain_head.state_root_hash, default_value=0)) public_key = self._identity_signer.get_public_key().as_hex() consensus = consensus_module.\ BlockPublisher(block_cache=self._block_cache, state_view_factory=self._state_view_factory, batch_publisher=self._batch_publisher, data_dir=self._data_dir, config_dir=self._config_dir, validator_id=public_key) batch_injectors = [] if self._batch_injector_factory is not None: batch_injectors = self._batch_injector_factory.create_injectors( chain_head.identifier) if batch_injectors: LOGGER.debug("Loaded batch injectors: %s", batch_injectors) block_header = BlockHeader( block_num=chain_head.block_num + 1, previous_block_id=chain_head.header_signature, signer_public_key=public_key) block_builder = BlockBuilder(block_header) if not consensus.initialize_block(block_builder.block_header): if not self._logging_states.consensus_not_ready: self._logging_states.consensus_not_ready = True LOGGER.debug("Consensus not ready to build candidate block.") return None if self._logging_states.consensus_not_ready: self._logging_states.consensus_not_ready = False LOGGER.debug("Consensus is ready to build candidate block.") # create a new scheduler scheduler = self._transaction_executor.create_scheduler( self._squash_handler, chain_head.state_root_hash) # build the TransactionCommitCache committed_txn_cache = TransactionCommitCache( self._block_cache.block_store) self._transaction_executor.execute(scheduler) self._candidate_block = _CandidateBlock( self._block_cache.block_store, consensus, scheduler, committed_txn_cache, block_builder, max_batches, batch_injectors, SettingsView(state_view), public_key) for batch in self._pending_batches: if self._candidate_block.can_add_batch: self._candidate_block.add_batch(batch) else: break def on_batch_received(self, batch): """ A new batch is received, send it for validation :param batch: the new pending batch :return: None """ with self._lock: self._queued_batch_ids = self._queued_batch_ids[:1] if self._permission_verifier.is_batch_signer_authorized(batch): self._pending_batches.append(batch) self._pending_batch_ids.append(batch.header_signature) self._pending_batch_gauge.set_value(len(self._pending_batches)) # if we are building a block then send schedule it for # execution. if self._candidate_block and \ self._candidate_block.can_add_batch: self._candidate_block.add_batch(batch) else: LOGGER.debug("Batch has an unauthorized signer. Batch: %s", batch.header_signature) def _rebuild_pending_batches(self, committed_batches, uncommitted_batches): """When the chain head is changed. This recomputes the list of pending transactions :param committed_batches: Batches committed in the current chain since the root of the fork switching from. :param uncommitted_batches: Batches that were committed in the old fork since the common root. """ if committed_batches is None: committed_batches = [] if uncommitted_batches is None: uncommitted_batches = [] committed_set = set([x.header_signature for x in committed_batches]) pending_batches = self._pending_batches self._pending_batches = [] self._pending_batch_ids = [] num_committed_batches = len(committed_batches) if num_committed_batches > 0: # Only update the average if either: # a. Not drained below the current average # b. Drained the queue, but the queue was not bigger than the # current running average remainder = len(self._pending_batches) - num_committed_batches if remainder > self._publish_count_average.value or \ num_committed_batches > self._publish_count_average.value: self._publish_count_average.update(num_committed_batches) # Uncommitted and pending disjoint sets # since batches can only be committed to a chain once. for batch in uncommitted_batches: if batch.header_signature not in committed_set: self._pending_batches.append(batch) self._pending_batch_ids.append(batch.header_signature) for batch in pending_batches: if batch.header_signature not in committed_set: self._pending_batches.append(batch) self._pending_batch_ids.append(batch.header_signature) def on_chain_updated(self, chain_head, committed_batches=None, uncommitted_batches=None): """ The existing chain has been updated, the current head block has changed. :param chain_head: the new head of block_chain, can be None if no block publishing is desired. :param committed_batches: the set of batches that were committed as part of the new chain. :param uncommitted_batches: the list of transactions if any that are now de-committed when the new chain was selected. :return: None """ try: with self._lock: if chain_head is not None: LOGGER.info('Now building on top of block: %s', chain_head) else: LOGGER.info('Block publishing is suspended until new ' 'chain head arrives.') self._chain_head = chain_head if self._candidate_block: self._candidate_block.cancel() self._candidate_block = None # we need to make a new # _CandidateBlock (if we can) since the block chain has updated # under us. if chain_head is not None: self._rebuild_pending_batches(committed_batches, uncommitted_batches) self._build_candidate_block(chain_head) self._pending_batch_gauge.set_value( len(self._pending_batches)) # pylint: disable=broad-except except Exception as exc: LOGGER.critical("on_chain_updated exception.") LOGGER.exception(exc) def on_check_publish_block(self, force=False): """Ask the consensus module if it is time to claim the candidate block if it is then, claim it and tell the world about it. :return: None """ try: with self._lock: if (self._chain_head is not None and self._candidate_block is None and self._pending_batches): self._build_candidate_block(self._chain_head) if self._candidate_block and ( force or self._candidate_block.has_pending_batches()) and \ self._candidate_block.check_publish_block(): pending_batches = [] # will receive the list of batches # that were not added to the block injected_batch_ids = \ self._candidate_block.injected_batch_ids last_batch = self._candidate_block.last_batch block = self._candidate_block.finalize_block( self._identity_signer, pending_batches) self._candidate_block = None # Update the _pending_batches to reflect what we learned. last_batch_index = self._pending_batches.index(last_batch) unsent_batches = \ self._pending_batches[last_batch_index + 1:] self._pending_batches = pending_batches + unsent_batches self._pending_batch_gauge.set_value( len(self._pending_batches)) if block: blkw = BlockWrapper(block) LOGGER.info("Claimed Block: %s", blkw) self._block_sender.send( blkw.block, keep_batches=injected_batch_ids) self._blocks_published_count.inc() # We built our candidate, disable processing until # the chain head is updated. Only set this if # we succeeded. Otherwise try again, this # can happen in cases where txn dependencies # did not validate when building the block. self.on_chain_updated(None) # pylint: disable=broad-except except Exception as exc: LOGGER.critical("on_check_publish_block exception.") LOGGER.exception(exc) def has_batch(self, batch_id): with self._lock: if batch_id in self._pending_batch_ids: return True if batch_id in self._queued_batch_ids: return True return False
class InstrumentedThreadPoolExecutor(ThreadPoolExecutor): def __init__(self, max_workers=None, name='', trace=None, metrics_registry=None): if trace is None: self._trace = 'SAWTOOTH_TRACE_LOGGING' in os.environ else: self._trace = trace self._name = name if name == '': self._name = 'Instrumented' LOGGER.debug('Creating thread pool executor %s', self._name) self._workers_in_use = atomic.Counter() self._max_workers = max_workers if self._max_workers is None: # This is the same default as ThreadPoolExecutor, but we want to # know how many workers there are for logging self._max_workers = multiprocessing.cpu_count() * 5 super().__init__(max_workers) if metrics_registry: # Tracks how many workers are already in use self._workers_already_in_use_gauge = GaugeWrapper( metrics_registry.gauge( '{}-threadpool.workers_already_in_use'.format(self._name))) # Tracks how long tasks take to run self._task_run_timer = TimerWrapper( metrics_registry.timer( '{}-threadpool.task_run_time'.format(self._name))) # Tracks how long tasks wait in the queue self._task_time_in_queue_timer = TimerWrapper( metrics_registry.timer( '{}-threadpool.task_time_in_queue'.format(self._name))) else: self._workers_already_in_use_gauge = GaugeWrapper() self._task_run_timer = TimerWrapper() self._task_time_in_queue_timer = TimerWrapper() def submit(self, fn, *args, **kwargs): time_in_queue_ctx = self._task_time_in_queue_timer.time() try: task_name = fn.__qualname__ except AttributeError: task_name = str(fn) if self._trace: task_details = '{}[{},{}]'.format(fn, args, kwargs) else: task_details = task_name def wrapper(): time_in_queue_ctx.stop() self._workers_already_in_use_gauge.set_value( self._workers_in_use.get_and_inc()) if self._trace: LOGGER.debug( '(%s) Executing task %s', self._name, task_details) with self._task_run_timer.time(): return_value = None try: return_value = fn(*args, **kwargs) # pylint: disable=broad-except except Exception: LOGGER.exception( '(%s) Unhandled exception during execution of task %s', self._name, task_details) self._workers_in_use.dec() if self._trace: LOGGER.debug( '(%s) Finished task %s', self._name, task_details) return return_value return super().submit(wrapper)
def __init__(self, block_store, gossip, cache_keep_time=1200, cache_purge_frequency=30, requested_keep_time=300, metrics_registry=None): """ :param block_store (dictionary) The block store shared with the journal :param gossip (gossip.Gossip) Broadcasts block and batch request to peers :param cache_keep_time (float) Time in seconds to keep values in TimedCaches. :param cache_purge_frequency (float) Time between purging the TimedCaches. :param requested_keep_time (float) Time in seconds to keep the ids of requested objects. WARNING this time should always be less than cache_keep_time or the validator can get into a state where it fails to make progress because it thinks it has already requested something that it is missing. """ self.gossip = gossip self.batch_cache = TimedCache(cache_keep_time, cache_purge_frequency) self.block_cache = BlockCache(block_store, cache_keep_time, cache_purge_frequency) self._block_store = block_store # avoid throwing away the genesis block self.block_cache[NULL_BLOCK_IDENTIFIER] = None self._seen_txns = TimedCache(cache_keep_time, cache_purge_frequency) self._incomplete_batches = TimedCache(cache_keep_time, cache_purge_frequency) self._incomplete_blocks = TimedCache(cache_keep_time, cache_purge_frequency) self._requested = TimedCache(requested_keep_time, cache_purge_frequency) self._on_block_received = None self._on_batch_received = None self._has_block = None self.lock = RLock() if metrics_registry: # Tracks how many times an unsatisfied dependency is found self._unsatisfied_dependency_count = CounterWrapper( metrics_registry.counter( 'completer.unsatisfied_dependency_count')) # Tracks the length of the completer's _seen_txns self._seen_txns_length = GaugeWrapper( metrics_registry.gauge( 'completer.seen_txns_length')) # Tracks the length of the completer's _incomplete_blocks self._incomplete_blocks_length = GaugeWrapper( metrics_registry.gauge( 'completer.incomplete_blocks_length')) # Tracks the length of the completer's _incomplete_batches self._incomplete_batches_length = GaugeWrapper( metrics_registry.gauge( 'completer.incomplete_batches_length')) else: self._unsatisfied_dependency_count = CounterWrapper() self._seen_txns_length = GaugeWrapper() self._incomplete_blocks_length = GaugeWrapper() self._incomplete_batches_length = GaugeWrapper()
class Completer(object): """ The Completer is responsible for making sure blocks are formally complete before they are delivered to the chain controller. A formally complete block is a block whose predecessor is in the block cache and all the batches are present in the batch list and in the order specified by the block header. If the predecessor or a batch is missing, a request message is sent sent out over the gossip network. It also checks that all batches have their dependencies satisifed, otherwise it will request the batch that has the missing transaction. """ def __init__(self, block_store, gossip, cache_keep_time=1200, cache_purge_frequency=30, requested_keep_time=300, metrics_registry=None): """ :param block_store (dictionary) The block store shared with the journal :param gossip (gossip.Gossip) Broadcasts block and batch request to peers :param cache_keep_time (float) Time in seconds to keep values in TimedCaches. :param cache_purge_frequency (float) Time between purging the TimedCaches. :param requested_keep_time (float) Time in seconds to keep the ids of requested objects. WARNING this time should always be less than cache_keep_time or the validator can get into a state where it fails to make progress because it thinks it has already requested something that it is missing. """ self.gossip = gossip self.batch_cache = TimedCache(cache_keep_time, cache_purge_frequency) self.block_cache = BlockCache(block_store, cache_keep_time, cache_purge_frequency) self._block_store = block_store # avoid throwing away the genesis block self.block_cache[NULL_BLOCK_IDENTIFIER] = None self._seen_txns = TimedCache(cache_keep_time, cache_purge_frequency) self._incomplete_batches = TimedCache(cache_keep_time, cache_purge_frequency) self._incomplete_blocks = TimedCache(cache_keep_time, cache_purge_frequency) self._requested = TimedCache(requested_keep_time, cache_purge_frequency) self._on_block_received = None self._on_batch_received = None self._has_block = None self.lock = RLock() if metrics_registry: # Tracks how many times an unsatisfied dependency is found self._unsatisfied_dependency_count = CounterWrapper( metrics_registry.counter( 'completer.unsatisfied_dependency_count')) # Tracks the length of the completer's _seen_txns self._seen_txns_length = GaugeWrapper( metrics_registry.gauge( 'completer.seen_txns_length')) # Tracks the length of the completer's _incomplete_blocks self._incomplete_blocks_length = GaugeWrapper( metrics_registry.gauge( 'completer.incomplete_blocks_length')) # Tracks the length of the completer's _incomplete_batches self._incomplete_batches_length = GaugeWrapper( metrics_registry.gauge( 'completer.incomplete_batches_length')) else: self._unsatisfied_dependency_count = CounterWrapper() self._seen_txns_length = GaugeWrapper() self._incomplete_blocks_length = GaugeWrapper() self._incomplete_batches_length = GaugeWrapper() def _complete_block(self, block): """ Check the block to see if it is complete and if it can be passed to the journal. If the block's predecessor is not in the block_cache the predecessor is requested and the current block is added to the the incomplete_block cache. If the block.batches and block.header.batch_ids are not the same length, the batch_id list is checked against the batch_cache to see if the batch_list can be built. If any batches are missing from the block and we do not have the batches in the batch_cache, they are requested. The block is then added to the incomplete_block cache. If we can complete the block, a new batch list is created in the correct order and added to the block. The block is now considered complete and is returned. If block.batches and block.header.batch_ids are the same length, the block's batch list needs to be in the same order as the block.header.batch_ids list. If the block has all of its expected batches but are not in the correct order, the batch list is rebuilt and added to the block. Once a block has the correct batch list it is added to the block_cache and is returned. """ if block.header_signature in self.block_cache: LOGGER.debug("Drop duplicate block: %s", block) return None if block.previous_block_id not in self.block_cache: if not self._has_block(block.previous_block_id): if block.previous_block_id not in self._incomplete_blocks: self._incomplete_blocks[block.previous_block_id] = [block] elif block not in \ self._incomplete_blocks[block.previous_block_id]: self._incomplete_blocks[block.previous_block_id] += [block] # We have already requested the block, do not do so again if block.previous_block_id in self._requested: return None LOGGER.debug("Request missing predecessor: %s", block.previous_block_id) self._requested[block.previous_block_id] = None self.gossip.broadcast_block_request(block.previous_block_id) return None # Check for same number of batch_ids and batches # If different starting building batch list, Otherwise there is a batch # that does not belong, block should be dropped. if len(block.batches) > len(block.header.batch_ids): LOGGER.debug("Block has extra batches. Dropping %s", block) return None # used to supplement batch_cache, contains batches already in block temp_batches = {} for batch in block.batches: temp_batches[batch.header_signature] = batch # The block is missing batches. Check to see if we can complete it. if len(block.batches) != len(block.header.batch_ids): building = True for batch_id in block.header.batch_ids: if batch_id not in self.batch_cache and \ batch_id not in temp_batches: # Request all missing batches if batch_id not in self._incomplete_blocks: self._incomplete_blocks[batch_id] = [block] elif block not in self._incomplete_blocks[batch_id]: self._incomplete_blocks[batch_id] += [block] # We have already requested the batch, do not do so again if batch_id in self._requested: return None self._requested[batch_id] = None self.gossip.broadcast_batch_by_batch_id_request(batch_id) building = False if not building: # The block cannot be completed. return None batches = self._finalize_batch_list(block, temp_batches) del block.batches[:] # reset batches with full list batches block.batches.extend(batches) if block.header_signature in self._requested: del self._requested[block.header_signature] return block else: batch_id_list = [x.header_signature for x in block.batches] # Check to see if batchs are in the correct order. if batch_id_list == list(block.header.batch_ids): if block.header_signature in self._requested: del self._requested[block.header_signature] return block # Check to see if the block has all batch_ids and they can be put # in the correct order elif sorted(batch_id_list) == sorted(list(block.header.batch_ids)): batches = self._finalize_batch_list(block, temp_batches) # Clear batches from block del block.batches[:] # reset batches with full list batches if batches is not None: block.batches.extend(batches) else: return None if block.header_signature in self._requested: del self._requested[block.header_signature] return block else: LOGGER.debug("Block.header.batch_ids does not match set of " "batches in block.batches Dropping %s", block) return None def _finalize_batch_list(self, block, temp_batches): batches = [] for batch_id in block.header.batch_ids: if batch_id in self.batch_cache: batches.append(self.batch_cache[batch_id]) elif batch_id in temp_batches: batches.append(temp_batches[batch_id]) else: return None return batches def _complete_batch(self, batch): valid = True dependencies = [] for txn in batch.transactions: txn_header = TransactionHeader() txn_header.ParseFromString(txn.header) for dependency in txn_header.dependencies: # Check to see if the dependency has been seen or is in the # current chain (block_store) if dependency not in self._seen_txns and not \ self.block_cache.block_store.has_transaction( dependency): self._unsatisfied_dependency_count.inc() # Check to see if the dependency has already been requested if dependency not in self._requested: dependencies.append(dependency) self._requested[dependency] = None if dependency not in self._incomplete_batches: self._incomplete_batches[dependency] = [batch] elif batch not in self._incomplete_batches[dependency]: self._incomplete_batches[dependency] += [batch] valid = False if not valid: self.gossip.broadcast_batch_by_transaction_id_request( dependencies) return valid def _add_seen_txns(self, batch): for txn in batch.transactions: self._seen_txns[txn.header_signature] = batch.header_signature self._seen_txns_length.set_value( len(self._seen_txns)) def _process_incomplete_batches(self, key): # Keys are transaction_id if key in self._incomplete_batches: batches = self._incomplete_batches[key] for batch in batches: self.add_batch(batch) del self._incomplete_batches[key] def _process_incomplete_blocks(self, key): # Keys are either a block_id or batch_id if key in self._incomplete_blocks: to_complete = deque() to_complete.append(key) while to_complete: my_key = to_complete.popleft() if my_key in self._incomplete_blocks: inc_blocks = self._incomplete_blocks[my_key] for inc_block in inc_blocks: if self._complete_block(inc_block): self.block_cache[inc_block.header_signature] = \ inc_block self._on_block_received(inc_block) to_complete.append(inc_block.header_signature) del self._incomplete_blocks[my_key] def set_on_block_received(self, on_block_received_func): self._on_block_received = on_block_received_func def set_on_batch_received(self, on_batch_received_func): self._on_batch_received = on_batch_received_func def set_chain_has_block(self, set_chain_has_block): self._has_block = set_chain_has_block def add_block(self, block): with self.lock: blkw = BlockWrapper(block) block = self._complete_block(blkw) if block is not None: self.block_cache[block.header_signature] = blkw self._on_block_received(blkw) self._process_incomplete_blocks(block.header_signature) self._incomplete_blocks_length.set_value( len(self._incomplete_blocks)) def add_batch(self, batch): with self.lock: if batch.header_signature in self.batch_cache: return if self._complete_batch(batch): self.batch_cache[batch.header_signature] = batch self._add_seen_txns(batch) self._on_batch_received(batch) self._process_incomplete_blocks(batch.header_signature) if batch.header_signature in self._requested: del self._requested[batch.header_signature] # If there was a batch waiting on this transaction, process # that batch for txn in batch.transactions: if txn.header_signature in self._incomplete_batches: if txn.header_signature in self._requested: del self._requested[txn.header_signature] self._process_incomplete_batches(txn.header_signature) self._incomplete_batches_length.set_value( len(self._incomplete_batches)) def get_chain_head(self): """Returns the block which is the current head of the chain. Returns: BlockWrapper: The head of the chain. """ with self.lock: return self._block_store.chain_head def get_block(self, block_id): with self.lock: if block_id in self.block_cache: return self.block_cache[block_id] return None def get_batch(self, batch_id): with self.lock: if batch_id in self.batch_cache: return self.batch_cache[batch_id] else: block_store = self.block_cache.block_store try: return block_store.get_batch(batch_id) except ValueError: return None def get_batch_by_transaction(self, transaction_id): with self.lock: if transaction_id in self._seen_txns: batch_id = self._seen_txns[transaction_id] return self.get_batch(batch_id) else: block_store = self.block_cache.block_store try: return block_store.get_batch_by_transaction(transaction_id) except ValueError: return None
class ChainController(object): """ To evaluating new blocks to determine if they should extend or replace the current chain. If they are valid extend the chain. """ def __init__(self, block_cache, block_sender, state_view_factory, transaction_executor, chain_head_lock, on_chain_updated, squash_handler, chain_id_manager, identity_signer, data_dir, config_dir, permission_verifier, chain_observers, thread_pool=None, metrics_registry=None): """Initialize the ChainController Args: block_cache: The cache of all recent blocks and the processing state associated with them. block_sender: an interface object used to send blocks to the network. state_view_factory: The factory object to create transaction_executor: The TransactionExecutor used to produce schedulers for batch validation. chain_head_lock: Lock to hold while the chain head is being updated, this prevents other components that depend on the chain head and the BlockStore from having the BlockStore change under them. This lock is only for core Journal components (BlockPublisher and ChainController), other components should handle block not found errors from the BlockStore explicitly. on_chain_updated: The callback to call to notify the rest of the system the head block in the chain has been changed. squash_handler: a parameter passed when creating transaction schedulers. chain_id_manager: The ChainIdManager instance. identity_signer: Private key for signing blocks. data_dir: path to location where persistent data for the consensus module can be stored. config_dir: path to location where config data for the consensus module can be found. chain_observers (list of :obj:`ChainObserver`): A list of chain observers. Returns: None """ self._lock = RLock() self._chain_head_lock = chain_head_lock self._block_cache = block_cache self._block_store = block_cache.block_store self._state_view_factory = state_view_factory self._block_sender = block_sender self._transaction_executor = transaction_executor self._notify_on_chain_updated = on_chain_updated self._squash_handler = squash_handler self._identity_signer = identity_signer self._data_dir = data_dir self._config_dir = config_dir self._blocks_processing = {} # a set of blocks that are # currently being processed. self._blocks_pending = {} # set of blocks that the previous block # is being processed. Once that completes this block will be # scheduled for validation. self._chain_id_manager = chain_id_manager self._chain_head = None self._permission_verifier = permission_verifier self._chain_observers = chain_observers if metrics_registry: self._chain_head_gauge = GaugeWrapper( metrics_registry.gauge('chain_head', default='no chain head')) self._committed_transactions_count = CounterWrapper( metrics_registry.counter('committed_transactions_count')) self._block_num_gauge = GaugeWrapper( metrics_registry.gauge('block_num')) else: self._chain_head_gauge = GaugeWrapper() self._committed_transactions_count = CounterWrapper() self._block_num_gauge = GaugeWrapper() self._block_queue = queue.Queue() self._thread_pool = \ InstrumentedThreadPoolExecutor(1) \ if thread_pool is None else thread_pool self._chain_thread = None # Only run this after all member variables have been bound self._set_chain_head_from_block_store() def _set_chain_head_from_block_store(self): try: self._chain_head = self._block_store.chain_head if self._chain_head is not None: LOGGER.info("Chain controller initialized with chain head: %s", self._chain_head) self._chain_head_gauge.set_value( self._chain_head.identifier[:8]) except Exception: LOGGER.exception( "Invalid block store. Head of the block chain cannot be" " determined") raise def start(self): self._set_chain_head_from_block_store() self._notify_on_chain_updated(self._chain_head) self._chain_thread = _ChainThread( chain_controller=self, block_queue=self._block_queue, block_cache=self._block_cache) self._chain_thread.start() def stop(self): if self._chain_thread is not None: self._chain_thread.stop() self._chain_thread = None if self._thread_pool is not None: self._thread_pool.shutdown(wait=True) def queue_block(self, block): """ New block has been received, queue it with the chain controller for processing. """ self._block_queue.put(block) @property def chain_head(self): return self._chain_head def _submit_blocks_for_verification(self, blocks): for blkw in blocks: state_view = BlockWrapper.state_view_for_block( self.chain_head, self._state_view_factory) consensus_module = \ ConsensusFactory.get_configured_consensus_module( self.chain_head.header_signature, state_view) validator = BlockValidator( consensus_module=consensus_module, new_block=blkw, block_cache=self._block_cache, state_view_factory=self._state_view_factory, done_cb=self.on_block_validated, executor=self._transaction_executor, squash_handler=self._squash_handler, identity_signer=self._identity_signer, data_dir=self._data_dir, config_dir=self._config_dir, permission_verifier=self._permission_verifier) self._blocks_processing[blkw.block.header_signature] = validator self._thread_pool.submit(validator.run) def on_block_validated(self, commit_new_block, result): """Message back from the block validator, that the validation is complete Args: commit_new_block (Boolean): whether the new block should become the chain head or not. result (Dict): Map of the results of the fork resolution. Returns: None """ try: with self._lock: new_block = result["new_block"] LOGGER.info("on_block_validated: %s", new_block) # remove from the processing list del self._blocks_processing[new_block.identifier] # Remove this block from the pending queue, obtaining any # immediate descendants of this block in the process. descendant_blocks = \ self._blocks_pending.pop(new_block.identifier, []) # if the head has changed, since we started the work. if result["chain_head"].identifier !=\ self._chain_head.identifier: LOGGER.info( 'Chain head updated from %s to %s while processing ' 'block: %s', result["chain_head"], self._chain_head, new_block) # If any immediate descendant blocks arrived while this # block was being processed, then submit them for # verification. Otherwise, add this block back to the # pending queue and resubmit it for verification. if descendant_blocks: LOGGER.debug( 'Verify descendant blocks: %s (%s)', new_block, [block.identifier[:8] for block in descendant_blocks]) self._submit_blocks_for_verification( descendant_blocks) else: LOGGER.debug('Verify block again: %s ', new_block) self._blocks_pending[new_block.identifier] = [] self._submit_blocks_for_verification([new_block]) # If the head is to be updated to the new block. elif commit_new_block: with self._chain_head_lock: self._chain_head = new_block # update the the block store to have the new chain self._block_store.update_chain(result["new_chain"], result["cur_chain"]) # make sure old chain is in the block_caches self._block_cache.add_chain(result["cur_chain"]) LOGGER.info( "Chain head updated to: %s", self._chain_head) self._chain_head_gauge.set_value( self._chain_head.identifier[:8]) self._committed_transactions_count.inc( result["num_transactions"]) self._block_num_gauge.set_value( self._chain_head.block_num) # tell the BlockPublisher else the chain is updated self._notify_on_chain_updated( self._chain_head, result["committed_batches"], result["uncommitted_batches"]) for batch in new_block.batches: if batch.trace: LOGGER.debug("TRACE %s: %s", batch.header_signature, self.__class__.__name__) # Submit any immediate descendant blocks for verification LOGGER.debug( 'Verify descendant blocks: %s (%s)', new_block, [block.identifier[:8] for block in descendant_blocks]) self._submit_blocks_for_verification(descendant_blocks) for block in reversed(result["new_chain"]): receipts = self._make_receipts(block.execution_results) # Update all chain observers for observer in self._chain_observers: observer.chain_update(block, receipts) # If the block was determine to be invalid. elif new_block.status == BlockStatus.Invalid: # Since the block is invalid, we will never accept any # blocks that are descendants of this block. We are going # to go through the pending blocks and remove all # descendants we find and mark the corresponding block # as invalid. while descendant_blocks: pending_block = descendant_blocks.pop() pending_block.status = BlockStatus.Invalid LOGGER.debug( 'Marking descendant block invalid: %s', pending_block) descendant_blocks.extend( self._blocks_pending.pop( pending_block.identifier, [])) # The block is otherwise valid, but we have determined we # don't want it as the chain head. else: LOGGER.info('Rejected new chain head: %s', new_block) # Submit for verification any immediate descendant blocks # that arrived while we were processing this block. LOGGER.debug( 'Verify descendant blocks: %s (%s)', new_block, [block.identifier[:8] for block in descendant_blocks]) self._submit_blocks_for_verification(descendant_blocks) # pylint: disable=broad-except except Exception: LOGGER.exception( "Unhandled exception in ChainController.on_block_validated()") def on_block_received(self, block): try: with self._lock: if self.has_block(block.header_signature): # do we already have this block return if self.chain_head is None: self._set_genesis(block) return # If we are already currently processing this block, then # don't bother trying to schedule it again. if block.identifier in self._blocks_processing: return self._block_cache[block.identifier] = block self._blocks_pending[block.identifier] = [] LOGGER.debug("Block received: %s", block) if block.previous_block_id in self._blocks_processing or \ block.previous_block_id in self._blocks_pending: LOGGER.debug('Block pending: %s', block) # if the previous block is being processed, put it in a # wait queue, Also need to check if previous block is # in the wait queue. pending_blocks = \ self._blocks_pending.get(block.previous_block_id, []) # Though rare, the block may already be in the # pending_block list and should not be re-added. if block not in pending_blocks: pending_blocks.append(block) self._blocks_pending[block.previous_block_id] = \ pending_blocks else: # schedule this block for validation. self._submit_blocks_for_verification([block]) # pylint: disable=broad-except except Exception: LOGGER.exception( "Unhandled exception in ChainController.on_block_received()") def has_block(self, block_id): with self._lock: if block_id in self._block_cache: return True if block_id in self._blocks_processing: return True if block_id in self._blocks_pending: return True return False def _set_genesis(self, block): # This is used by a non-genesis journal when it has received the # genesis block from the genesis validator if block.previous_block_id == NULL_BLOCK_IDENTIFIER: chain_id = self._chain_id_manager.get_block_chain_id() if chain_id is not None and chain_id != block.identifier: LOGGER.warning("Block id does not match block chain id %s. " "Cannot set initial chain head.: %s", chain_id[:8], block.identifier[:8]) else: state_view = self._state_view_factory.create_view() consensus_module = \ ConsensusFactory.get_configured_consensus_module( NULL_BLOCK_IDENTIFIER, state_view) validator = BlockValidator( consensus_module=consensus_module, new_block=block, block_cache=self._block_cache, state_view_factory=self._state_view_factory, done_cb=self.on_block_validated, executor=self._transaction_executor, squash_handler=self._squash_handler, identity_signer=self._identity_signer, data_dir=self._data_dir, config_dir=self._config_dir, permission_verifier=self._permission_verifier) valid = validator.validate_block(block) if valid: if chain_id is None: self._chain_id_manager.save_block_chain_id( block.identifier) self._block_store.update_chain([block]) self._chain_head = block self._notify_on_chain_updated(self._chain_head) else: LOGGER.warning("The genesis block is not valid. Cannot " "set chain head: %s", block) else: LOGGER.warning("Cannot set initial chain head, this is not a " "genesis block: %s", block) def _make_receipts(self, results): receipts = [] for result in results: receipt = TransactionReceipt() receipt.data.extend([data for data in result.data]) receipt.state_changes.extend(result.state_changes) receipt.events.extend(result.events) receipt.transaction_id = result.signature receipts.append(receipt) return receipts