def new_block(self, block, peers): if len(peers) > 0: # start consensus for active peers self.start_consensus(block) return False if self.check_consensus(block): # at this point state PREPARED LOGGER.info('Passed consensus check in state PREPARED: %s ', _short_id(block.block_id.hex())) if block.block_num == 3 and self._try_branch: # try make branch pause current block for main branch self._make_branch = True self._try_branch = False if self._freeze: self._freeze_block = block else: self.check_block(block.block_id) else: self.check_block( block.block_id ) # this message send chain controller message for continue block validation # waiting block valid message return True else: LOGGER.info('Failed consensus blk=%s branch=%s', _short_id(block.block_id.hex()), self._head_id[:8]) self.reset_state() self.fail_block(block.block_id) return False
def _handle_invalid_block(self, block_id): try: block = self._get_block(block_id) signer_id = block.signer_id.hex() bid = block_id.hex() LOGGER.info('=> INVALID_BLOCK:Received id=%s signer=%s\n', _short_id(bid), signer_id[:8]) if bid in self._new_heads: self._new_heads.pop(bid) if signer_id == self._validator_id: if block.previous_block_id in self._branches: branch = self._branches[block.previous_block_id] if bid in self._peers_branches: del self._peers_branches[bid] if block.block_num == 0: branch.reset_state() else: LOGGER.info('=> INVALID_BLOCK: DONT DO reset \n') else: LOGGER.info( '=> INVALID_BLOCK: external block=%s branches=%s \n', bid[:8], [key[:8] for key in self._peers_branches.keys()]) if bid in self._peers_branches: branch = self._peers_branches[bid] branch = self.reset_state() del self._peers_branches[bid] LOGGER.info('=> INVALID_BLOCK: branches=%s \n', [key[:8] for key in self._peers_branches.keys()]) except: LOGGER.info('=> INVALID_BLOCK: undefined id=%s\n', _short_id(block_id.hex())) self.reset_state()
def __str__(self): return ("Block(" + ", ".join([ "block_num: {}".format(self.block_num), "block_id: {}".format(_short_id(self.block_id.hex())), "previous_id: {}".format(_short_id(self.previous_id.hex())), "signer_id: {}".format(_short_id(self.signer_id.hex())), "payload: {}".format(self.payload), "summary: {}".format(self.summary.hex()), ]) + ")")
def __init__(self, block): # fields that come with consensus blocks self.block_id = block.block_id self.previous_id = block.previous_id self.signer_id = block.signer_id self.block_num = block.block_num self.payload = block.payload self.summary = block.summary # fields that bgt requires identifier = block.block_id.hex() previous_block_id = block.previous_id.hex() signer_public_key = block.signer_id.hex() self.identifier = identifier self.header_signature = identifier self.previous_block_id = previous_block_id self.signer_public_key = signer_public_key LOGGER.debug('BgtBlock: block=%s.%s(%s)', self.block_num, _short_id(identifier), signer_public_key[:8]) self.header = _DummyHeader(consensus=block.payload, signer_public_key=signer_public_key, previous_block_id=previous_block_id) # this is a trick self.state_root_hash = block.block_id
def resolve_fork(self, chain_head, block): LOGGER.info( 'Branch[%s] Choosing between chain heads current:%s new:%s', self._head_id[:8], _short_id(chain_head.block_id.hex()), _short_id(block.block_id.hex())) if self.switch_forks(chain_head, block): LOGGER.info('Committing block=%s for BRANCH=%s', _short_id(block.block_id.hex()), self._head_id[:8]) self.commit_block(block.block_id) self._committing = True return True else: LOGGER.info('Ignoring block=%s for BRANCH=%s', _short_id(block.block_id.hex()), self._head_id[:8]) self.reset_state() self.ignore_block(block.block_id) return False
def _handle_valid_block(self, block_id): LOGGER.info('=> VALID_BLOCK:Received %s', _short_id(block_id.hex())) block = self._get_block(block_id) self._pending_forks_to_resolve.push(block) self._committing = False self._process_pending_forks() LOGGER.info('VALID_BLOCK pending_forks DONE.')
def _broadcast(self, payload, msg_type, block_id): # broadcast message block_id = block_id.hex() #state.shift_sequence_number(block_id,self._consensus_state_store) mgs_type = CONSENSUS_MSG[msg_type] LOGGER.debug("BROADCAST =>> '%s' for block_id=%s", mgs_type, _short_id(block_id)) self._service.broadcast(mgs_type, payload.SerializeToString())
def _handle_peer_connected(self, block): #block = BgtBlock(block) pinfo = ConsensusNotifyPeerConnected() #info = pinfo.ParseFromString(block) pid = block.peer_id.hex() if pid not in self._peers and pid != self._validator_id: self._peers[pid] = True LOGGER.info('Connected peer with ID=%s\n', _short_id(pid))
def switch_forks(self, cur_fork_head, new_fork_head): '''"compare_forks" is not an intuitive name.''' LOGGER.debug('BgtOracle: switch_forks %s', cur_fork_head) if self._can_fail_block and new_fork_head.block_num == 3: self._can_fail_block = False LOGGER.debug('BgtOracle: MAKE forks FAIL FOR TEST ONLY %s\n', cur_fork_head) return False # FOR TESTING ONLY if new_fork_head.block_num > cur_fork_head.block_num or ( new_fork_head.block_num == cur_fork_head.block_num and new_fork_head.block_id > cur_fork_head.block_id): LOGGER.debug( 'BgtOracle: switch_forks new-num=%s cur-num=%s new-id=%s cur-id=%s', new_fork_head.block_num, cur_fork_head.block_num, _short_id(new_fork_head.block_id.hex()), _short_id(cur_fork_head.block_id.hex())) return True elif new_fork_head.block_num < cur_fork_head.block_num: # chain_block = cur_fork_head LOGGER.debug( 'BgtOracle: new_fork_head.block_num < cur_fork_head.block_num') while (True): chain_block = BgtBlock( self._service.get_blocks([chain_block.previous_id ])[chain_block.previous_id]) if chain_block.block_num == new_fork_head.block_num: LOGGER.debug('BgtOracle: found block') break if new_fork_head.block_id > chain_block.block_id: LOGGER.debug('BgtOracle: switch to new forks') return True return False fork_resolver = BgtForkResolver( block_cache=self._block_cache, state_view_factory=self._state_view_factory, data_dir=self._data_dir, config_dir=self._config_dir, validator_id=self._validator_id) return fork_resolver.compare_forks(cur_fork_head, new_fork_head)
def _resolve_fork(self, block): # ask head for branch bid bid = block.previous_block_id bbid = bytes.fromhex(bid) signer_id = block.signer_id.hex() block_id = block.block_id.hex() if signer_id == self._validator_id: if bid in self._branches: # head could be already changed - we can get new head for this branch chain_head = self._get_chain_head(bbid) branch = self._branches[bid] if branch.resolve_fork(chain_head, block): self._committing = True else: self.reset_state() else: LOGGER.info('HEAD FOR BLOCK=%s(%s) was changed', block.block_num, _short_id(block_id), signer_id[:8]) else: # external block LOGGER.info('EXTERNAL BLOCK=%s(%s) num=%s prev=%s', _short_id(block_id), signer_id[:8], block.block_num, bid[:8]) if block_id in self._peers_branches: # head could be already changed - we can get new head for this branch chain_head = self._get_chain_head( None if bid == NULL_BLOCK_IDENTIFIER else bbid) # ask head for branch branch = self._peers_branches[block_id] LOGGER.info('RESOLVE FORK for BLOCK=%s(%s) branch=%s', _short_id(block_id), signer_id[:8], branch.ind) if branch.resolve_fork(chain_head, block): self._committing = True else: self.reset_state()
def _my_finalize_block(self): """ in case DAG we should return parent for block which is ready because we ask one of the initialized blocks """ summary, parent_id = self._summarize_block() if summary is None: #LOGGER.debug('Block not ready to be summarized') return None bid = parent_id.hex() LOGGER.debug('Can FINALIZE NOW parent=%s branches=%s', _short_id(bid), [key[:8] for key in self._branches.keys()]) if bid in self._branches: LOGGER.debug('FINALIZE BRANCH=%s', bid[:8]) branch = self._branches[bid] branch.finalize_block(parent_id, summary) """
def __init__(self, service, component_endpoint, config_dir, data_dir, key_dir): self._config_dir = config_dir self._data_dir = data_dir LOGGER.debug('BgtOracle: Stream key_dir=%s', key_dir) self._signer = _load_identity_signer(key_dir, 'validator') self._validator_id = self._signer.get_public_key().as_hex() self._service = service LOGGER.debug('BgtOracle: Stream component_endpoint=%s', component_endpoint) stream = Stream(component_endpoint) self._block_cache = _BlockCacheProxy(service, stream) self._state_view_factory = _StateViewFactoryProxy(service) self._batch_publisher = _BatchPublisherProxy(stream, self._signer) self._publisher = None self._can_fail_block = False #True LOGGER.debug('BgtOracle:validator=%s init DONE', _short_id(self._validator_id))
def finalize_block(self, parent_id, summary): consensus = b'Devmode' #self._oracle.finalize_block(summary) if consensus is None: return None try: # say to validator that we are ready to finalize this block block_id = self._service.finalize_block(parent_id, consensus) LOGGER.info('Finalized summary=%s block_id=%s BRANCH=%s', summary, _short_id(block_id.hex()), self._head_id[:8]) self._building = True # ONLY for testing new version - normal True self._published = True # ONLY for testing new version- normal True # broadcast #LOGGER.debug('broadcast ...') #self._service.broadcast('message_type',b'payload') 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
def commit_block(self, block_id): LOGGER.warning("commit_block: block_id=%s\n", _short_id(block_id.hex())) self._service.commit_block(block_id)
def _handle_committed_block(self, block_id): block_id = block_id.hex() def _update_head_branch(prev_num, block_num): LOGGER.info(' _update_head_branch check=%s', prev_num) for key, branch in self._branches.items(): if branch.block_num == prev_num: LOGGER.info(' update chain head for BRANCH=%s -> %s', branch.ind, block_id[:8]) branch = self._branches.pop(key) branch._parent_id = block_id branch._block_num = block_num self._branches[block_id] = branch return True return False LOGGER.info( '=> BLOCK_COMMIT Chain head updated to %s, abandoning block in progress heads=%s', _short_id(block_id), [key[:8] for key in self._new_heads.keys()]) # for DAG new head for branch will be this block_id # and we should use it for asking chain head for this branch if block_id in self._new_heads: hid = self._new_heads.pop(block_id) # hid is parent of this block LOGGER.info( ' update chain head for BRANCH=%s->%s branches=%s+%s', hid[:8], block_id[:8], [key[:8] for key in self._branches.keys()], [key[:8] for key in self._peers_branches.keys()]) if hid in self._branches: branch = self._branches.pop(hid) branch.cancel_block(block_id) # change parent_id too branch.reset_state() self._branches[block_id] = branch self._TOTAL_BLOCK += 1 if block_id in self._peers_branches: del self._peers_branches[block_id] LOGGER.info( ' set new head=%s for BRANCH=%s TOTAL=%s peers branches=%s', block_id[:8], hid[:8], self._TOTAL_BLOCK, [key[:8] for key in self._peers_branches.keys()]) else: # external block LOGGER.info('head updated for=%s peers branches=%s', _short_id(block_id), [key[:8] for key in self._peers_branches.keys()]) if block_id in self._peers_branches: branch = self._peers_branches[block_id] branch.cancel_block(block_id) # change parent_id too branch.reset_state() del self._peers_branches[block_id] # update _branches block = self._get_block(bytes.fromhex(block_id)) prev = block while not _update_head_branch(prev.block_num, block.block_num): prev = self._get_block( bytes.fromhex(prev.previous_block_id)) LOGGER.info('=> BLOCK_COMMIT prepre=%s pre=%s branches=%s+%s', len(self._pre_prepare_msgs), len(self._prepare_msgs), [key[:8] for key in self._branches.keys()], [key[:8] for key in self._peers_branches.keys()]) self._process_pending_forks() self.reset_state()
def _initialize_block(self, branch=None, new_branch=None, is_new=False): LOGGER.debug('BgtEngine: _initialize_block branch[%s] is_new=%s', branch.hex()[:8] if branch is not None else None, is_new) """ getting addition chain head for DAG in case call _get_chain_head(parent_head) where parent_head is point for making chain branch """ try: # for switch current branch to another node add argument new_branch chain_head = self._get_chain_head( branch, new_branch, is_new ) # get MAIN chain_head. chain_head.block_id is ID of parent's block if not self._chain_head: self._chain_head = chain_head except exceptions.TooManyBranch: LOGGER.debug( 'BgtEngine: CANT CREATE NEW BRANCH (limit is reached)') self._make_branch = False return False except exceptions.NoChainHead: # head was updated or not commited yet LOGGER.debug('BgtEngine: CANT GET CHAIN HEAD for=%s', branch.hex()[:8] if branch is not None else None) return False LOGGER.debug('BgtEngine: _initialize_block ID=%s chain_head=(%s)', _short_id(chain_head.block_id.hex()), chain_head) #initialize = True #self._oracle.initialize_block(chain_head) #if initialize: try: self._service.initialize_block(previous_id=chain_head.block_id) # for remove this branch to another point bid = branch.hex( ) if branch is not None else chain_head.block_id.hex() parent = chain_head.previous_block_id block_num = chain_head.block_num if bid in self._branches: #branch = self._branches[bid] branch = self._branches[bid] branch._published = True branch._parent_id = parent branch._block_num = block_num if new_branch is not None: del self._branches[bid] self._branches[new_branch.hex()] = branch LOGGER.debug('BgtEngine: _initialize_block USE Branch[%s]=%s', branch.ind, bid[:8]) else: LOGGER.debug('BgtEngine: _initialize_block NEW Branch[%s]=%s', self._num_branches, bid[:8]) self._branches[bid] = self.create_branch( bid, parent, block_num) self._branches[bid]._try_branch = self._make_branch except exceptions.UnknownBlock: LOGGER.debug('BgtEngine: _initialize_block ERROR UnknownBlock') #return False except exceptions.InvalidState: LOGGER.debug('BgtEngine: _initialize_block ERROR InvalidState') self._skip = True return False except Exception as ex: LOGGER.debug( 'BgtEngine: _initialize_block HEAD=%s.%s ERROR %s!!!\n', chain_head.block_num, chain_head.block_id.hex()[:8], ex) return False return True
def check_block(self, block_id): # send in case of consensus was reached LOGGER.warning("check_block: block_id=%s\n", _short_id(block_id.hex())) self._service.check_blocks([block_id])
def _handle_new_block(self, block): block = BgtBlock(block) block_id = block.block_id.hex() signer_id = block.signer_id.hex() summary = block.summary.hex() block_num = block.block_num num_peers = len(self._peers) LOGGER.info('=> NEW_BLOCK:Received block=%s.%s signer=%s summ=%s', block_num, _short_id(block_id), signer_id[:8], summary[:8]) def check_consensus(): if num_peers > 0 and summary in self._prepare_msgs: blocks = self._prepare_msgs[summary] self.check_consensus(blocks, block_num, summary, num_peers) if signer_id == self._validator_id: # find branch for this block if block.previous_block_id in self._branches: branch = self._branches[block.previous_block_id] self._peers_branches[ block_id] = branch # add ref on branch for own block result = branch.new_block(block, self._peers) if result == Consensus.done: # refer for block on branch self._new_heads[block_id] = block.previous_block_id LOGGER.info(' NEW_HEAD=%s for BRANCh=%s', block_id[:8], block.previous_block_id[:8]) elif result == Consensus.fail: LOGGER.info('Failed consensus check: %s', block_id[:8]) else: LOGGER.info('consensus started for block=%s->%s', block_id[:8], block.previous_block_id[:8]) self._new_heads[block_id] = block.previous_block_id if block_id in self._pre_prepare_msgs: """ free PRE_PREPARE mess here. Now we have new_block and will ignore PRE_PREPARE """ msg = self._pre_prepare_msgs[ block_id] # self._pre_prepare_msgs.pop(block_id) branch._send_prepare(msg) check_consensus() # Don't reset now - wait message INVALID_BLOCK #self.reset_state() else: # external block from another node if block_id not in self._peers_branches: LOGGER.info('EXTERNAL NEW BLOCK=%s.%s peer=%s', block_num, _short_id(block_id), _short_id(signer_id)) branch = self.create_branch('', '', block_num) self._peers_branches[block_id] = branch LOGGER.info('START CONSENSUS for BLOCK=%s(%s) branch=%s', _short_id(block_id), signer_id[:8], branch.ind) branch.new_block(block, self._peers) self._new_heads[block_id] = block.previous_block_id LOGGER.info(' NEW_HEAD=%s for BRANCh=%s', block_id[:8], block.previous_block_id[:8]) if block_id in self._pre_prepare_msgs: """ free PRE_PREPARE mess here. Now we have new_block and will ignore PRE_PREPARE """ msg = self._pre_prepare_msgs[ block_id] # self._pre_prepare_msgs.pop(block_id) branch._send_prepare(msg) # check maybe all messages arrived check_consensus() else: LOGGER.info( 'EXTERNAL BLOCK=%s.%s num=%s peer=%s IGNORE(already has)', block_num, _short_id(block_id), _short_id(signer_id))