def process_view_change_message(self, msg: ViewChange, frm: str): result = self._validate(msg, frm) if result == STASH_WAITING_VIEW_CHANGE: self._stashed_vc_msgs.setdefault(msg.viewNo, 0) self._stashed_vc_msgs[msg.viewNo] += 1 if self._data.quorums.view_change.is_reached(self._stashed_vc_msgs[msg.viewNo]) and \ not self._data.waiting_for_new_view: self._bus.send(StartViewChange(msg.viewNo)) if result != PROCESS: return result, None self._logger.info("{} processing {} from {}".format(self, msg, frm)) self.view_change_votes.add_view_change(msg, frm) vca = ViewChangeAck(viewNo=msg.viewNo, name=getNodeName(frm), digest=view_change_digest(msg)) self.view_change_votes.add_view_change_ack( vca, getNodeName(self._data.name)) if self._data.is_primary: self._send_new_view_if_needed() return PROCESS, None primary_node_name = getNodeName(self._data.primary_name) self._logger.info("{} sending {}".format(self, vca)) self._network.send(vca, [primary_node_name]) self._finish_view_change_if_needed() return PROCESS, None
def test_non_primary_responds_to_view_change_message_with_view_change_ack_to_new_primary( internal_bus, external_bus, some_item, other_item, validators, primary, view_change_service_builder, initial_view_no, is_master): # TODO: Need to decide on how we handle this case if not is_master: return next_view_no = initial_view_no + 1 non_primary_name = some_item(validators, exclude=[primary(next_view_no)]) service = view_change_service_builder(non_primary_name) internal_bus.send(NeedViewChange()) external_bus.sent_messages.clear() vc = create_view_change(initial_view_no) frm = other_item(validators, exclude=[non_primary_name]) external_bus.process_incoming(vc, generateName(frm, service._data.inst_id)) assert len(external_bus.sent_messages) == 1 msg, dst = external_bus.sent_messages[0] assert dst == [getNodeName(service._data.primary_name)] assert isinstance(msg, ViewChangeAck) assert msg.viewNo == vc.viewNo assert msg.name == frm assert msg.digest == view_change_digest(vc)
def create_new_view_from_vc(vc, validators, checkpoint=None, batches=None): vc_digest = view_change_digest(vc) vcs = [[node_name, vc_digest] for node_name in validators] checkpoint = checkpoint or vc.checkpoints[0] batches = batches or vc.prepared return NewView(vc.viewNo, sorted(vcs, key=itemgetter(0)), checkpoint, batches)
def process_view_change_message(self, msg: ViewChange, frm: str): result = self._validate(msg, frm) if result != PROCESS: return result, None self._logger.info("{} processing {} from {}".format(self, msg, frm)) self.view_change_votes.add_view_change(msg, frm) vca = ViewChangeAck(viewNo=msg.viewNo, name=getNodeName(frm), digest=view_change_digest(msg)) self.view_change_votes.add_view_change_ack( vca, getNodeName(self._data.name)) if self._data.is_primary: self._send_new_view_if_needed() return PROCESS, None primary_node_name = getNodeName(self._data.primary_name) self._logger.info("{} sending {}".format(self, vca)) self._network.send(vca, [primary_node_name]) self._finish_view_change_if_needed() return PROCESS, None
def test_view_change_digest_is_256_bit_hexdigest(random): digest = view_change_digest( create_view_change(initial_view_no=0, stable_cp=random.integer(0, 10000), batches=create_batches(view_no=0))) assert isinstance(digest, str) assert len(digest) == 64 assert all(v in string.hexdigits for v in digest)
def _view_change_acks(vc, vc_frm, primary, count): digest = view_change_digest(vc) non_senders = [ name for name in validators if name not in [vc_frm, primary] ] ack_frms = random.sample(non_senders, count) return [(ViewChangeAck(viewNo=vc.viewNo, name=vc_frm, digest=digest), ack_frm) for ack_frm in ack_frms]
def test_different_view_change_messages_have_different_digests(random): batches = create_batches(view_no=0) assert view_change_digest(create_view_change(initial_view_no=0, stable_cp=100, batches=batches)) != \ view_change_digest(create_view_change(initial_view_no=1, stable_cp=100, batches=batches)) assert view_change_digest(create_view_change(initial_view_no=1, stable_cp=100, batches=batches)) != \ view_change_digest(create_view_change(initial_view_no=1, stable_cp=101, batches=batches)) assert view_change_digest( create_view_change(initial_view_no=1, stable_cp=100, batches=create_batches(view_no=0))) != \ view_change_digest(create_view_change(initial_view_no=1, stable_cp=100, batches=create_batches(view_no=1))) assert view_change_digest(create_view_change(initial_view_no=0, stable_cp=100, batches=batches)) == \ view_change_digest(create_view_change(initial_view_no=0, stable_cp=100, batches=batches))
def test_process_missing_message_view_change(message_req_service: MessageReqService, external_bus, data, internal_bus, view_change_message: ViewChange): frm = "frm" confused_node = "confused_node" inst_id = data.inst_id digest = view_change_digest(view_change_message) missing_msg = MissingMessage(msg_type=VIEW_CHANGE, key=(frm, digest), inst_id=inst_id, dst=[confused_node], stash_data=None) internal_bus.send(missing_msg) assert len(external_bus.sent_messages) == 1 assert external_bus.sent_messages[0] == (MessageReq(VIEW_CHANGE, {f.INST_ID.nm: inst_id, f.DIGEST.nm: digest, f.NAME.nm: frm}), [confused_node])
def test_process_message_req_view_change(message_req_service: MessageReqService, external_bus, data: ConsensusSharedData, view_change_message: ViewChange): frm = "frm" digest = view_change_digest(view_change_message) message_req = MessageReq(**{ f.MSG_TYPE.nm: VIEW_CHANGE, f.PARAMS.nm: {f.INST_ID.nm: data.inst_id, f.DIGEST.nm: digest, f.NAME.nm: frm}, }) data.view_change_votes.add_view_change(view_change_message, frm) external_bus.process_incoming(message_req, frm) assert len(external_bus.sent_messages) == 1 assert external_bus.sent_messages[0] == (MessageRep(message_req.msg_type, message_req.params, view_change_message._asdict()), [frm])
def _validate_view_change(self, msg: ViewChange, frm: str, params): if msg is None: raise IncorrectMessageForHandlingException(msg, reason='received null', log_method=self._logger.debug) key = (params["name"], view_change_digest(msg)) if key not in self.requested_messages: raise IncorrectMessageForHandlingException(msg, reason='Had either not requested this msg or already ' 'received the msg for {}'.format(key), log_method=self._logger.debug) self._received_vc.setdefault(key, set()) self._received_vc[key].add(frm) if not self._data.quorums.weak.is_reached(len(self._received_vc[key])) and frm != params["name"]: raise IncorrectMessageForHandlingException(msg, reason='Count of VIEW_CHANGE messages {} ' 'is not enough for quorum.'.format(msg), log_method=self._logger.trace)
def test_process_message_rep_view_change_from_one(message_req_service: MessageReqService, external_bus, data, view_change_message: ViewChange): frm = "frm" inst_id = data.inst_id digest = view_change_digest(view_change_message) key = (frm, digest) message_req_service.handlers[VIEW_CHANGE].requested_messages[key] = None message_rep_from_primary = MessageRep(**{ f.MSG_TYPE.nm: VIEW_CHANGE, f.PARAMS.nm: {f.INST_ID.nm: inst_id, f.DIGEST.nm: digest, f.NAME.nm: frm}, f.MSG.nm: dict(view_change_message.items()) }) frm = "frm" network_handler = Mock() external_bus.subscribe(ViewChange, network_handler) network_handler.assert_not_called() message_req_service.process_message_rep(message_rep_from_primary, frm) network_handler.assert_called_once_with(view_change_message, frm)
def test_new_view_from_malicious(view_change_service_builder, primary, initial_view_no, validators): """ This test shows situation, when there is quorum of correct NEW_VIEW msgs and NEW_VIEW msg from malicious primary. In this case, view_change will be completed by quorum of the same NEW_VIEW msgs not by NEW_VIEW from malicious """ proposed_view_no = initial_view_no + 1 primary_name = primary(proposed_view_no) without_primary = [v for v in validators if v != primary_name] vcs_name = without_primary[0] vcs = view_change_service_builder(vcs_name) vcs._data.is_master = True vcs.process_need_view_change(NeedViewChange(view_no=proposed_view_no)) vc_not_malicious = vcs.view_change_votes._get_vote(vcs_name).view_change not_malicious_nv = create_new_view_from_vc(vc_not_malicious, without_primary, checkpoint=vc_not_malicious.checkpoints[-1]) vc_from_malicious = create_view_change(initial_view_no, stable_cp=20, batches=[]) for i in range(0, len(without_primary)): vcs.view_change_votes.add_view_change(vc_not_malicious, without_primary[i]) vcs._data.new_view_votes.add_new_view(not_malicious_nv, without_primary[i]) vcs.view_change_votes.add_view_change(vc_from_malicious, primary_name) malicious_nv = NewView(viewNo=proposed_view_no, viewChanges=[[primary_name, view_change_digest(vc_from_malicious)]], checkpoint=Checkpoint(instId=0, viewNo=initial_view_no, seqNoStart=10, seqNoEnd=20, digest=cp_digest(20)), batches=[]) vcs.process_new_view_message(malicious_nv, "{}:{}".format(primary_name, 0)) assert not vcs._data.waiting_for_new_view
def create_view_change_acks(vc, vc_frm, senders): digest = view_change_digest(vc) senders = [name for name in senders if name != vc_frm] return [(ViewChangeAck(viewNo=vc.viewNo, name=vc_frm, digest=digest), ack_frm) for ack_frm in senders]
def _create(self, msg: Dict, **kwargs): message = super()._create(msg) if view_change_digest(message) != kwargs[f.DIGEST.nm]: raise MismatchedMessageReplyException return message