def consensus_state_for_block_id(block_id, block_cache, state_view_factory, consensus_state_store, node=None, force=True): """Returns the consensus state for the block referenced by block ID, creating it from the consensus state history if necessary. Args: block_id (str): The ID of the block for which consensus state will be returned. block_cache (BlockCache): The block store cache state_view_factory (StateViewFactory): A factory that can be used to create state view object corresponding to blocks consensus_state_store (ConsensusStateStore): The consensus state store that is used to store interim consensus state created up to resulting consensus state Returns: ConsensusState object representing the consensus state for the block referenced by block_id """ #LOGGER.debug('ConsensusState: consensus_state_for_block_id for block_id=%s',block_id) consensus_state = None #previous_wait_certificate = None #blocks = collections.OrderedDict() # Starting at the chain head, walk the block store backwards until we # either get to the root or we get a block for which we have already # created consensus state current_id = block_id #block = ConsensusState._block_for_id(block_id=current_id,block_cache=block_cache) #LOGGER.debug("ConsensusState: BLOCK for block_id=%s block=%s",_short_id(current_id),block) #LOGGER.debug("ConsensusState: ASK STATE for block_id=%s",current_id) consensus_state = consensus_state_store.get(block_id=current_id) if consensus_state is not None: LOGGER.debug( "ConsensusState: FOUND CONSENSUS_STATE for block_id=%s", _short_id(current_id)) pass elif force: LOGGER.debug( "ConsensusState: CREATE CONSENSUS_STATE node=%s for block_id=%s", node, _short_id(current_id)) consensus_state = ConsensusState(node) else: return None return consensus_state """
def _my_finalize_block(self): summary = self._summarize_block() if summary is None: #LOGGER.debug('Block not ready to be summarized') return None LOGGER.debug('Can FINALIZE NOW') consensus = b'devmode' #self._oracle.finalize_block(summary) if consensus is None: return None try: block_id = self._service.finalize_block(consensus) LOGGER.info('Finalized %s with ',_short_id(block_id.hex())) #json.loads(consensus.decode()) self._building = True # broadcast return block_id except exceptions.BlockNotReady: LOGGER.debug('Block not ready to be finalized') return None except exceptions.InvalidState: LOGGER.warning('block cannot be finalized') return None except Exception as err: LOGGER.warning("error=%s",err) return None
def push(self, block): try: index = self._queue.index(block.previous_id) except ValueError: self._queue.insert(0, block.block_id) self._blocks[block.block_id] = block LOGGER.debug('PendingForks PUSH block_id=%s',_short_id(block.block_id.hex())) return try: del self._blocks[block.previous_id] LOGGER.debug('PendingForks DEL block_id=%s',_short_id(block.previous_id.hex())) except KeyError: pass LOGGER.debug('PendingForks PUSH block_id=%s INSTEAD prev',_short_id(block.block_id.hex())) self._queue[index] = block.block_id self._blocks[block.block_id] = block
def _handle_peer_connected(self, block): """ Messages about new peers """ #block = PbftBlock(block) if not self._is_peer_connected: LOGGER.debug('=> PEER_CONNECTED: peer=%s', _short_id(block.peer_id.hex())) self._is_peer_connected = True
def _handle_valid_block(self, block_id): LOGGER.info('=> VALID_BLOCK:Received block_id=%s\n', _short_id(block_id.hex())) block = self._get_block(block_id) self._resolve_fork(block) #self._pending_forks_to_resolve.push(block) #self._process_pending_forks() # after pending_forks block could be ingored self._oracle.message_consensus_handler(Message.CONSENSUS_NOTIFY_BLOCK_VALID,block)
def _resolve_fork(self, block): chain_head = self._get_chain_head() LOGGER.info('Choosing between chain heads -- current: %s -- new: %s',_short_id(chain_head.block_id.hex()),_short_id(block.block_id.hex())) if self._switch_forks(chain_head, block): LOGGER.info('stop process _process_pending_forks for block=%s', _short_id(block.block_id.hex())) #self._commit_block(block.block_id) # stop process _process_pending_forks() self._committing = True else: LOGGER.info('Ignoring block=%s', _short_id(block.block_id.hex())) # mark block as ignored self._oracle.ignore_block(block) #self._fail_block(block.block_id) #self._cancel_block() # try cancel self._ignore_block(block.block_id)
def _check_consensus(self, block= None): #if not self._is_peer_connected : return True if self._oracle.check_consensus(self._block): LOGGER.info('PbftEngine:Passed consensus check: %s', _short_id(self._block.block_id.hex())) self._check = False # stop checking #self._check_block(block.block_id) else: #LOGGER.info('PbftEngine: Failed consensus check: %s', self._block.block_id.hex()) #self._fail_block(self._block.block_id) pass #self._start_consensus(block) return True
def _finalize_block(self): summary = self._summarize_block() if summary is None: #LOGGER.debug('Block not ready to be summarized') return None # only after that point we can receive new block msg LOGGER.debug('Block ready to be finalized summary=%s\n',_short_id(summary.hex())) consensus = self._oracle.finalize_block(summary) if consensus is None: return None self._block_id = None # should do initialize try: """ Stop adding batches to the current block and finalize it. Include the given consensus data in the block. If this call is successful, a BlockNew update will be received with the new block """ block_id = self._service.finalize_block(consensus) """ now we have block_id for block with summary """ #LOGGER.info('Finalized %s with %s',block_id.hex(),json.loads(consensus.decode())) LOGGER.info('Finalized block_id=%s DONE\n',_short_id(block_id.hex())) return block_id except exceptions.BlockNotReady: LOGGER.debug('PbftEngine:: Block not ready to be finalized') return None except exceptions.InvalidState: LOGGER.warning('PbftEngine::block cannot be finalized in InvalidState') self._building = False LOGGER.debug('PbftEngine: _cancel_block') self._cancel_block() return None except Exception as err: LOGGER.warning("PbftEngine::error=%s",err) return None
def _initialize_block(self): LOGGER.debug('PbftEngine: START _initialize_block') chain_head = self._get_chain_head() initialize = self._oracle.initialize_block(chain_head) LOGGER.debug('PbftEngine: _initialize_block initialize=%s ID=%s num=%s',initialize,_short_id(chain_head.block_id.hex()),chain_head.block_num) if initialize : try: if self._block_id != chain_head.block_id: self._block_id = chain_head.block_id # save current block id self._block_num = chain_head.block_num # open new block Candidate start from chain_head.block_id self._service.initialize_block(previous_id=chain_head.block_id) LOGGER.debug('PbftEngine: PROXY _initialize_block DONE\n') else: self._block_id = chain_head.block_id LOGGER.debug('PbftEngine: PROXY _initialize_block ALREADY WAS DONE\n') except exceptions.UnknownBlock: LOGGER.debug('BgtEngine: PROXY _initialize_block ERROR UnknownBlock') #return False except exceptions.InvalidState : LOGGER.debug('BgtEngine: PROXY _initialize_block ERROR InvalidState') except Exception as ex: LOGGER.debug('BgtEngine: PROXY _initialize_block ERROR %s',ex) return False return True #initialize
def _handle_committed_block(self, block_id): LOGGER.info('=> COMMITTED_BLOCK:Chain head updated to %s, abandoning block in progress',_short_id(block_id.hex())) block = self._get_block(block_id) self._oracle.message_consensus_handler(Message.CONSENSUS_NOTIFY_BLOCK_COMMIT,block) # make sure that bloock candidate is empty self._cancel_block() self.reset_loop_state()
def _handle_invalid_block(self,block_id): LOGGER.info('=> INVALID_BLOCK:Received id=%s\n', _short_id(block_id.hex())) block = self._get_block(block_id) self._oracle.message_consensus_handler(Message.CONSENSUS_NOTIFY_BLOCK_INVALID,block)
def _try_to_publish(self): if self._check: # check consensus after NEW BLOCK self._check_consensus() if self._published: return if not self._building: LOGGER.debug('_initialize_block OPEN NEW BLOCK CANDIDATE\n') if self._initialize_block(): self._building = True LOGGER.debug('_initialize_block DONE') if self._building: #LOGGER.debug('PbftEngine: _check_publish_block ..') if self._check_publish_block(): #LOGGER.debug('PbftEngine: _finalize_block ..') #self._check = False block_id = self._finalize_block() if block_id: LOGGER.info("After finalize we have new Published block id=%s\n", _short_id(block_id.hex())) self._published = True #self._building = False #self._consensus = True # ready for consensus """
def _fail_block(self, block_id): LOGGER.warning("PbftEngine: _fail_block: block_id=%s",_short_id(block_id.hex())) self._service.fail_block(block_id)
def _check_block(self, block_id): # just check there is this block or not LOGGER.warning("PbftEngine: _check_block: block_id=%s",_short_id(block_id)) self._service.check_blocks([block_id])
def finalize_block(self, block_header): """Finalize a block to be claimed. Provide any signatures and data updates that need to be applied to the block before it is signed and broadcast to the network. Args: block_header (BlockHeader): The block header for the candidate block that needs to be finalized. Returns: Boolean: True if the candidate block good and should be generated. False if the block should be abandoned. """ summary = block_header.hex() LOGGER.debug('FINALIZE BLOCK CANDIDATE: block_id=%s summary=%s',_short_id(self._block_id),_short_id(summary)) if isinstance(block_header, bytes): """ At this point _block_id is previous and summary for current block save state with block_header key """ """ state = ConsensusState.consensus_state_for_block_id( block_id=summary, block_cache=self._block_cache, state_view_factory=self._state_view_factory, consensus_state_store=self._consensus_state_store, force=True ) LOGGER.debug('FINALIZE BLOCK CANDIDATE: state=%s',state) # save in store state.set_consensus_state_for_block_id(summary,self._consensus_state_store) """ """ state_view = BlockWrapper.state_view_for_block( block_wrapper=self._block_cache.block_store.chain_head, state_view_factory=self._state_view_factory) """ # We need to create a wait certificate for the block and # then serialize that into the block header consensus field. if _VREG_: active_key = self._pbft_key_state_store.active_key pbft_key_state = self._pbft_key_state_store[active_key] sealed_signup_data = pbft_key_state.sealed_signup_data consensus = b'pbft' LOGGER.debug('PbftBlockPublisher::finalize_block isinstance DONE') return consensus # To compute the block hash, we are going to perform a hash using the # previous block ID and the batch IDs contained in the block hasher = hashlib.sha256(block_header.previous_block_id.encode()) for batch_id in block_header.batch_ids: hasher.update(batch_id.encode()) block_hash = hasher.hexdigest() # Using the current chain head, we need to create a state view so we # can create a PBFT enclave. state_view = BlockWrapper.state_view_for_block( block_wrapper=self._block_cache.block_store.chain_head, state_view_factory=self._state_view_factory) if _VREG_: # We need to create a wait certificate for the block and then serialize # that into the block header consensus field. active_key = self._pbft_key_state_store.active_key pbft_key_state = self._pbft_key_state_store[active_key] sealed_signup_data = pbft_key_state.sealed_signup_data #block_header.consensus = b'pbft' LOGGER.debug('PbftBlockPublisher::finalize_block: DONE') return True
def initialize_block(self, block_header): """Do initialization necessary for the consensus to claim a block, this may include initiating voting activities, starting proof of work hash generation, or create a PBFT wait timer. Args: block_header (BlockHeader): The BlockHeader to initialize. Returns: Boolean: True if the candidate block should be built. False if no candidate should be built. """ LOGGER.debug('PbftBlockPublisher::initialize_block previous_block_id=%s (%s)',_short_id(block_header.previous_block_id),block_header) # If the previous block ID matches our cached one, that means that we # have already determined that even if we initialize the requested # block we would not be able to claim it. So, instead of wasting time # doing all of the checking again, simply short-circuit the failure so # that the validator can go do something more useful. if block_header.previous_block_id == PbftBlockPublisher._previous_block_id: LOGGER.debug("PbftBlockPublisher::initialize_block block_header.previous_block_id == PbftBlockPublisher._previous_block_id TRUE") return False PbftBlockPublisher._previous_block_id = block_header.previous_block_id # Using the current chain head, we need to create a state view so we # can create a PBFT enclave. if False: state_view = BlockWrapper.state_view_for_block( block_wrapper=self._block_cache.block_store.chain_head, state_view_factory=self._state_view_factory) pbft_settings_view = PbftSettingsView(state_view) LOGGER.debug("PbftBlockPublisher::pbft_settings_view node=%s",pbft_settings_view.pbft_node) #self._node = pbft_settings_view.pbft_node consensus_state = ConsensusState.consensus_state_for_block_id( block_id=block_header.previous_block_id, block_cache=self._block_cache, state_view_factory=self._state_view_factory, consensus_state_store=self._consensus_state_store, node=self._node ) # shift into PrePrepare state consensus_state.next_step() #consensus_state.mark_as_own() consensus_state.set_consensus_state_for_block_id(block_header.previous_block_id,self._consensus_state_store) self._block_id = block_header.previous_block_id #consensus_state.set_node(self._node) LOGGER.debug("PbftBlockPublisher::initialize_block GET CONSENSUS_STATE=%s for block_id=%s ",consensus_state,_short_id(block_header.previous_block_id)) # start # Get our validator registry entry to see what PBFT public key # other validators think we are using. if _VREG_: validator_registry_view = ValidatorRegistryView(state_view) validator_info = None try: validator_id = block_header.signer_public_key validator_info = validator_registry_view.get_validator_info(validator_id=validator_id) except KeyError: pass # If we don't have a validator registry entry, then check the active # key. If we don't have one, then we need to sign up. If we do have # one, then our validator registry entry has not percolated through the # system, so nothing to to but wait. active_pbft_public_key = self._pbft_key_state_store.active_key if validator_info is None: if active_pbft_public_key is None: LOGGER.debug('PbftBlockPublisher::initialize_block No public key found, so going to register new signup information') self._register_signup_information(block_header=block_header) else: # Check if we need to give up on this registration attempt try: nonce = self._pbft_key_state_store[active_pbft_public_key].signup_nonce except (ValueError, AttributeError): self._pbft_key_state_store.active_key = None LOGGER.warning('PbftBlockPublisher::initialize_block Pbft Key State Store had inaccessible or ' 'corrupt active key [%s] clearing ' 'key.', active_pbft_public_key) return False LOGGER.debug('PbftBlockPublisher::initialize_block Check if we need to give up on this registration attempt') self._handle_registration_timeout( block_header=block_header, pbft_enclave_module=None,#pbft_enclave_module, state_view=state_view, signup_nonce=nonce, pbft_public_key=active_pbft_public_key ) LOGGER.debug("PbftBlockPublisher::initialize_block validator_info NONE") return True #False # Retrieve the key state corresponding to the PBFT public key in our # validator registry entry. pbft_key_state = None try: pbft_key_state = self._pbft_key_state_store[validator_info.signup_info.pbft_public_key] except (ValueError, KeyError): pass # If there is no key state associated with the PBFT public key that # other validators think we should be using, then we need to create # new signup information as we have no way whatsoever to publish # blocks that other validators will accept. LOGGER.debug("PbftBlockPublisher::check pbft_key_state=%s",pbft_key_state) if pbft_key_state is None: LOGGER.debug('PbftBlockPublisher::initialize_block PBFT public key %s...%s in validator registry not found in key state store. Sign up again', validator_info.signup_info.pbft_public_key[:8], validator_info.signup_info.pbft_public_key[-8:]) self._register_signup_information(block_header=block_header) # We need to put fake information in the key state store for the # PBFT public key the other validators think we are using so that # we don't try to keep signing up. However, we are going to mark # that key state store entry as being refreshed so that we will # never actually try to use it. dummy_data = b64encode(b'No sealed signup data').decode('utf-8') self._pbft_key_state_store[validator_info.signup_info.pbft_public_key] = PbftKeyState( sealed_signup_data=dummy_data, has_been_refreshed=True, signup_nonce='unknown') return False # Check the key state. If it is marked as being refreshed, then we are # waiting until our PBFT public key is updated in the validator # registry and therefore we cannot publish any blocks. if _VREG_ and pbft_key_state.has_been_refreshed: LOGGER.debug( 'PBFT public key %s...%s has been refreshed. Wait for new ' 'key to show up in validator registry.', validator_info.signup_info.pbft_public_key[:8], validator_info.signup_info.pbft_public_key[-8:]) # Check if we need to give up on this registration attempt self._handle_registration_timeout( block_header=block_header, pbft_enclave_module=pbft_enclave_module, state_view=state_view, signup_nonce=pbft_key_state.signup_nonce, pbft_public_key=active_pbft_public_key ) return False # If the PBFT public key in the validator registry is not the active # one, then we need to switch the active key in the key state store. if _VREG_: if validator_info.signup_info.pbft_public_key != active_pbft_public_key: active_pbft_public_key = validator_info.signup_info.pbft_public_key self._pbft_key_state_store.active_key = active_pbft_public_key # Ensure that the enclave is using the appropriate keys try: signup_data = json2dict(base64.b64decode(pbft_key_state.sealed_signup_data.encode()).decode()) unsealed_pbft_public_key = signup_data.get('pbft_public_key') except SystemError: # Signup data is unuseable LOGGER.error( 'Could not unseal signup data associated with PPK: %s..%s', active_pbft_public_key[:8], active_pbft_public_key[-8:]) self._pbft_key_state_store.active_key = None return False LOGGER.debug("PbftBlockPublisher::unsealed_pbft_public_key=%s ~ %s signup_data=%s",unsealed_pbft_public_key,active_pbft_public_key,signup_data) assert active_pbft_public_key == unsealed_pbft_public_key LOGGER.debug('Using PBFT public key: %s...%s',active_pbft_public_key[:8],active_pbft_public_key[-8:]) LOGGER.debug('Unseal signup data: %s...%s',pbft_key_state.sealed_signup_data[:8],pbft_key_state.sealed_signup_data[-8:]) """ LOGGER.debug("PbftBlockPublisher::initialize_block ADD CONSENSUS_STATE for block_id=%s",block_header.previous_block_id) consensus_state = ConsensusState.consensus_state_for_block_id( block_id=block_header.previous_block_id, block_cache=self._block_cache, state_view_factory=self._state_view_factory, consensus_state_store=self._consensus_state_store, pbft_enclave_module=None, ) """ #pbft_settings_view = PbftSettingsView(state_view) #LOGGER.debug("PbftBlockPublisher::pbft_settings_view node=%s",pbft_settings_view.pbft_node) # If our signup information does not pass the freshness test, then we # know that other validators will reject any blocks we try to claim so # we need to try to sign up again. # Using the consensus state for the block upon which we want to # build, check to see how many blocks we have claimed on this chain # with this PBFT key. If we have hit the key block claim limit, then # we need to check if the key has been refreshed. # We need to create a wait timer for the block...this is what we # will check when we are asked if it is time to publish the block pbft_key_state = self._pbft_key_state_store[active_pbft_public_key] sealed_signup_data = pbft_key_state.sealed_signup_data # At this point, we know that if we are able to claim the block we are # initializing, we will not be prevented from doing so because of PBFT # policies. self._wait_timer = 20 self._wait_timer = 20 PbftBlockPublisher._previous_block_id = None block_header.consensus = b"pbft" LOGGER.debug('PbftBlockPublisher::initialize_block DONE _wait_timer=%s',self._wait_timer) self._block_header = block_header return True