def get_next_primary_name(txnPoolNodeSet, expected_view_no): selector = RoundRobinPrimariesSelector() inst_count = len(txnPoolNodeSet[0].replicas) next_p_name = selector.select_primaries( expected_view_no, inst_count, txnPoolNodeSet[0].poolManager.node_names_ordered_by_rank())[0] return next_p_name
def __init__(self, data: ConsensusSharedData, timer: TimerService, bus: InternalBus, network: ExternalBus, stasher: StashingRouter): self._config = getConfig() self._logger = getlogger() self._data = data self._new_view_builder = NewViewBuilder(self._data) self._timer = timer self._bus = bus self._network = network self._router = stasher self._new_view = None # type: Optional[NewView] self._resend_inst_change_timer = RepeatingTimer(self._timer, self._config.NEW_VIEW_TIMEOUT, partial(self._propose_view_change, Suspicions.INSTANCE_CHANGE_TIMEOUT.code), active=False) self._router.subscribe(ViewChange, self.process_view_change_message) self._router.subscribe(ViewChangeAck, self.process_view_change_ack_message) self._router.subscribe(NewView, self.process_new_view_message) self._old_prepared = {} # type: Dict[int, BatchID] self._old_preprepared = {} # type: Dict[int, List[BatchID]] self._primaries_selector = RoundRobinPrimariesSelector() self._subscription = Subscription() self._subscription.subscribe(self._bus, NeedViewChange, self.process_need_view_change)
def __init__(self, data: ConsensusSharedData, timer: TimerService, bus: InternalBus, network: ExternalBus, stasher: StashingRouter): self._config = getConfig() self._logger = getlogger() self._data = data self._new_view_builder = NewViewBuilder(self._data) self._timer = timer self._bus = bus self._network = network self._router = stasher self._votes = ViewChangeVotesForView(self._data.quorums) self._new_view = None # type: Optional[NewView] self._router.subscribe(ViewChange, self.process_view_change_message) self._router.subscribe(ViewChangeAck, self.process_view_change_ack_message) self._router.subscribe(NewView, self.process_new_view_message) self._old_prepared = {} # type: Dict[int, BatchID] self._old_preprepared = {} # type: Dict[int, List[BatchID]] self._primaries_selector = RoundRobinPrimariesSelector() self._subscription = Subscription() self._subscription.subscribe(self._bus, NeedViewChange, self.process_need_view_change)
def __init__(self, tmpdir, config=None): self.basedirpath = tmpdir self.name = 'Node1' self.internal_bus = InternalBus() self.db_manager = DatabaseManager() self.timer = QueueTimer() self.f = 1 self.replicas = dict() self.requests = Requests() self.rank = None self.allNodeNames = [self.name, 'Node2', 'Node3', 'Node4'] self.nodeReg = { name: HA("127.0.0.1", 0) for name in self.allNodeNames } self.nodeIds = [] self.totalNodes = len(self.allNodeNames) self.mode = Mode.starting self.config = config or getConfigOnce() self.nodeStatusDB = None self.internal_bus = InternalBus() self.quorums = Quorums(self.totalNodes) self.nodestack = FakeSomething(connecteds=set(self.allNodeNames)) self.write_manager = FakeSomething() self.replicas = { 0: Replica(node=self, instId=0, isMaster=True, config=self.config), 1: Replica(node=self, instId=1, isMaster=False, config=self.config), 2: Replica(node=self, instId=2, isMaster=False, config=self.config) } self.requiredNumberOfInstances = 2 self._found = False self.ledgerManager = LedgerManager(self) ledger0 = FakeLedger(0, 10) ledger1 = FakeLedger(1, 5) self.ledgerManager.addLedger(0, ledger0) self.ledgerManager.addLedger(1, ledger1) self.quorums = Quorums(self.totalNodes) self.view_changer = create_view_changer(self) self.primaries_selector = RoundRobinPrimariesSelector() self.metrics = NullMetricsCollector() # For catchup testing self.catchup_rounds_without_txns = 0 self.view_change_in_progress = False self.ledgerManager.last_caught_up_3PC = (0, 0) self.master_last_ordered_3PC = (0, 0) self.seqNoDB = {} # callbacks self.onBatchCreated = lambda self, *args, **kwargs: True
def __init__(self, data: ConsensusSharedData, timer: TimerService, bus: InternalBus, network: ExternalBus, stasher: StashingRouter): self._config = getConfig() self._logger = getlogger() self._data = data self._new_view_builder = NewViewBuilder(self._data) self._timer = timer self._bus = bus self._network = network self._router = stasher # Last successful viewNo. # In some cases view_change process can be uncompleted in time. # In that case we want to know, which viewNo was successful (last completed view_change) self.last_completed_view_no = self._data.view_no self._resend_inst_change_timer = RepeatingTimer( self._timer, self._config.NEW_VIEW_TIMEOUT, partial(self._propose_view_change_not_complete_in_time), active=False) self._router.subscribe(ViewChange, self.process_view_change_message) self._router.subscribe(ViewChangeAck, self.process_view_change_ack_message) self._router.subscribe(NewView, self.process_new_view_message) self._old_prepared = {} # type: Dict[int, BatchID] self._old_preprepared = {} # type: Dict[int, List[BatchID]] self._primaries_selector = RoundRobinPrimariesSelector() self._subscription = Subscription() self._subscription.subscribe(self._bus, NeedViewChange, self.process_need_view_change)
def primary_in_view(validators, view_no): f = (len(validators) - 1) // 3 return RoundRobinPrimariesSelector().select_primaries( view_no=view_no, instance_count=f + 1, validators=validators)[0]
class ViewChangeService: def __init__(self, data: ConsensusSharedData, timer: TimerService, bus: InternalBus, network: ExternalBus, stasher: StashingRouter): self._config = getConfig() self._logger = getlogger() self._data = data self._new_view_builder = NewViewBuilder(self._data) self._timer = timer self._bus = bus self._network = network self._router = stasher # Last successful viewNo. # In some cases view_change process can be uncompleted in time. # In that case we want to know, which viewNo was successful (last completed view_change) self.last_completed_view_no = self._data.view_no self._resend_inst_change_timer = RepeatingTimer( self._timer, self._config.NEW_VIEW_TIMEOUT, partial(self._propose_view_change_not_complete_in_time), active=False) self._router.subscribe(ViewChange, self.process_view_change_message) self._router.subscribe(ViewChangeAck, self.process_view_change_ack_message) self._router.subscribe(NewView, self.process_new_view_message) self._old_prepared = {} # type: Dict[int, BatchID] self._old_preprepared = {} # type: Dict[int, List[BatchID]] self._primaries_selector = RoundRobinPrimariesSelector() self._subscription = Subscription() self._subscription.subscribe(self._bus, NeedViewChange, self.process_need_view_change) def __repr__(self): return self._data.name @property def view_change_votes(self): return self._data.view_change_votes def process_need_view_change(self, msg: NeedViewChange): self._logger.info("{} processing {}".format(self, msg)) # 1. calculate new viewno view_no = msg.view_no if view_no is None: view_no = self._data.view_no + 1 # 2. Do cleanup before new view change starts self._clean_on_view_change_start() # 3. Update shared data self._data.view_no = view_no self._data.waiting_for_new_view = True self._data.primaries = self._primaries_selector.select_primaries( view_no=self._data.view_no, instance_count=self._data.quorums.f + 1, validators=self._data.validators) for i, primary_name in enumerate(self._data.primaries): self._logger.display( "{} selected primary {} for instance {} (view {})".format( PRIMARY_SELECTION_PREFIX, primary_name, i, self._data.view_no), extra={ "cli": "ANNOUNCE", "tags": ["node-election"] }) old_primary = self._data.primary_name self._data.primary_name = generateName( self._data.primaries[self._data.inst_id], self._data.inst_id) if not self._data.is_master: return if old_primary and self._data.primary_name == old_primary: self._logger.info("Selected master primary is the same with the " "current master primary (new_view {}). " "Propose a new view {}".format( self._data.view_no, self._data.view_no + 1)) self._propose_view_change(Suspicions.INCORRECT_NEW_PRIMARY.code) # 4. Build ViewChange message vc = self._build_view_change_msg() # 5. Send ViewChangeStarted via internal bus to update other services self._logger.info("{} sending {}".format(self, vc)) self._bus.send(ViewChangeStarted(view_no=self._data.view_no)) # 6. Send ViewChange msg to other nodes (via external bus) self._network.send(vc) self.view_change_votes.add_view_change(vc, self._data.name) # 7. Unstash messages for view change self._router.process_all_stashed(STASH_WAITING_VIEW_CHANGE) # 8. Restart instance change timer self._resend_inst_change_timer.stop() self._resend_inst_change_timer.start() def _clean_on_view_change_start(self): self._clear_old_batches(self._old_prepared) self._clear_old_batches(self._old_preprepared) self.view_change_votes.clear() self._data.new_view = None def _clear_old_batches(self, batches: Dict[int, Any]): for pp_seq_no in list(batches.keys()): if pp_seq_no <= self._data.stable_checkpoint: del batches[pp_seq_no] def _build_view_change_msg(self): for batch_id in self._data.prepared: self._old_prepared[batch_id.pp_seq_no] = batch_id prepared = sorted(list(self._old_prepared.values())) for new_bid in self._data.preprepared: pretenders = self._old_preprepared.get(new_bid.pp_seq_no, []) pretenders = [ bid for bid in pretenders if bid.pp_digest != new_bid.pp_digest ] pretenders.append(new_bid) self._old_preprepared[new_bid.pp_seq_no] = pretenders preprepared = sorted( [bid for bids in self._old_preprepared.values() for bid in bids]) return ViewChange(viewNo=self._data.view_no, stableCheckpoint=self._data.stable_checkpoint, prepared=prepared, preprepared=preprepared, checkpoints=list(self._data.checkpoints)) 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 process_view_change_ack_message(self, msg: ViewChangeAck, frm: str): result = self._validate(msg, frm) if result != PROCESS: return result, None self._logger.info("{} processing {} from {}".format(self, msg, frm)) if not self._data.is_primary: return PROCESS, None self.view_change_votes.add_view_change_ack(msg, frm) self._send_new_view_if_needed() return PROCESS, None def process_new_view_message(self, msg: NewView, frm: str): result = self._validate(msg, frm) if result != PROCESS: return result, None self._logger.info("{} processing {} from {}".format(self, msg, frm)) if frm != self._data.primary_name: self._logger.info( "{} Received NewView {} for view {} from non-primary {}; expected primary {}" .format(self._data.name, msg, self._data.view_no, frm, self._data.primary_name)) return DISCARD, "New View from non-Primary" self._data.new_view = msg self._finish_view_change_if_needed() return PROCESS, None def _validate(self, msg: Union[ViewChange, ViewChangeAck, NewView], frm: str) -> int: # TODO: Proper validation if not self._data.is_master: return DISCARD if msg.viewNo < self._data.view_no: return DISCARD if msg.viewNo == self._data.view_no and not self._data.waiting_for_new_view: return DISCARD if msg.viewNo > self._data.view_no: return STASH_WAITING_VIEW_CHANGE return PROCESS def _send_new_view_if_needed(self): confirmed_votes = self.view_change_votes.confirmed_votes if not self._data.quorums.view_change.is_reached(len(confirmed_votes)): return view_changes = [ self.view_change_votes.get_view_change(*v) for v in confirmed_votes ] cp = self._new_view_builder.calc_checkpoint(view_changes) if cp is None: return batches = self._new_view_builder.calc_batches(cp, view_changes) if batches is None: return if cp not in self._data.checkpoints: return nv = NewView(viewNo=self._data.view_no, viewChanges=sorted(confirmed_votes, key=itemgetter(0)), checkpoint=cp, batches=batches) self._logger.info("{} sending {}".format(self, nv)) self._network.send(nv) self._data.new_view = nv self._finish_view_change() def _finish_view_change_if_needed(self): if self._data.new_view is None: return view_changes = [] for name, vc_digest in self._data.new_view.viewChanges: vc = self.view_change_votes.get_view_change(name, vc_digest) # We don't have needed ViewChange, so we cannot validate NewView if vc is None: self._request_view_change_message((name, vc_digest)) return view_changes.append(vc) cp = self._new_view_builder.calc_checkpoint(view_changes) if cp is None or cp != self._data.new_view.checkpoint: # New primary is malicious self._logger.info( "{} Received invalid NewView {} for view {}: expected checkpoint {}" .format(self._data.name, self._data.new_view, self._data.view_no, cp)) self._propose_view_change( Suspicions.NEW_VIEW_INVALID_CHECKPOINTS.code) return batches = self._new_view_builder.calc_batches(cp, view_changes) if batches != self._data.new_view.batches: # New primary is malicious self._logger.info( "{} Received invalid NewView {} for view {}: expected batches {}" .format(self._data.name, self._data.new_view, self._data.view_no, batches)) self._propose_view_change(Suspicions.NEW_VIEW_INVALID_BATCHES.code) return self._finish_view_change() def _finish_view_change(self): # Update shared data self._data.waiting_for_new_view = False self._data.prev_view_prepare_cert = self._data.new_view.batches[-1].pp_seq_no \ if self._data.new_view.batches else 0 # Cancel View Change timeout task self._resend_inst_change_timer.stop() # send message to other services self._bus.send( NewViewAccepted(view_no=self._data.new_view.viewNo, view_changes=self._data.new_view.viewChanges, checkpoint=self._data.new_view.checkpoint, batches=self._data.new_view.batches)) self.last_completed_view_no = self._data.view_no def _propose_view_change_not_complete_in_time(self): self._propose_view_change(Suspicions.INSTANCE_CHANGE_TIMEOUT.code) if self._data.new_view is None: self._request_new_view_message(self._data.view_no) def _propose_view_change(self, suspision_code): proposed_view_no = self._data.view_no # TODO: For some reason not incrementing view_no in most cases leads to lots of failing/flaky tests # if suspicion == Suspicions.INSTANCE_CHANGE_TIMEOUT or not self.view_change_in_progress: if suspision_code != Suspicions.STATE_SIGS_ARE_NOT_UPDATED or not self._data.waiting_for_new_view: proposed_view_no += 1 self._logger.info( "{} proposing a view change to {} with code {}".format( self, proposed_view_no, suspision_code)) msg = InstanceChange(proposed_view_no, suspision_code) self._network.send(msg) def _request_new_view_message(self, view_no): self._bus.send( MissingMessage(msg_type=NEW_VIEW, key=view_no, inst_id=self._data.inst_id, dst=[getNodeName(self._data.primary_name)], stash_data=None)) def _request_view_change_message(self, key): self._bus.send( MissingMessage(msg_type=VIEW_CHANGE, key=key, inst_id=self._data.inst_id, dst=None, stash_data=None))
class ViewChangeService: def __init__(self, data: ConsensusSharedData, timer: TimerService, bus: InternalBus, network: ExternalBus): self._config = getConfig() self._logger = getlogger() self._data = data self._new_view_builder = NewViewBuilder(self._data) self._timer = timer self._bus = bus self._network = network self._router = StashingRouter(self._config.VIEW_CHANGE_SERVICE_STASH_LIMIT) self._votes = ViewChangeVotesForView(self._data.quorums) self._new_view = None # type: Optional[NewView] self._router.subscribe(ViewChange, self.process_view_change_message) self._router.subscribe(ViewChangeAck, self.process_view_change_ack_message) self._router.subscribe(NewView, self.process_new_view_message) self._router.subscribe_to(network) self._old_prepared = {} # type: Dict[int, BatchID] self._old_preprepared = {} # type: Dict[int, List[BatchID]] self._primaries_selector = RoundRobinPrimariesSelector() def __repr__(self): return self._data.name def start_view_change(self, view_no: Optional[int] = None): if view_no is None: view_no = self._data.view_no + 1 self._clear_old_batches(self._old_prepared) self._clear_old_batches(self._old_preprepared) for batch_id in self._data.prepared: self._old_prepared[batch_id.pp_seq_no] = batch_id prepared = sorted([tuple(bid) for bid in self._old_prepared.values()]) for new_bid in self._data.preprepared: pretenders = self._old_preprepared.get(new_bid.pp_seq_no, []) pretenders = [bid for bid in pretenders if bid.pp_digest != new_bid.pp_digest] pretenders.append(new_bid) self._old_preprepared[new_bid.pp_seq_no] = pretenders preprepared = sorted([tuple(bid) for bids in self._old_preprepared.values() for bid in bids]) self._data.view_no = view_no self._data.waiting_for_new_view = True self._data.primary_name = self._primaries_selector.select_primaries(view_no=self._data.view_no, instance_count=self._data.quorums.f + 1, validators=self._data.validators)[0] self._data.preprepared.clear() self._data.prepared.clear() self._votes.clear() self._new_view = None vc = ViewChange( viewNo=self._data.view_no, stableCheckpoint=self._data.stable_checkpoint, prepared=prepared, preprepared=preprepared, checkpoints=list(self._data.checkpoints) ) self._network.send(vc) self._votes.add_view_change(vc, self._data.name) self._router.process_all_stashed() def process_view_change_message(self, msg: ViewChange, frm: str): result = self._validate(msg, frm) if result != PROCESS: return result self._votes.add_view_change(msg, frm) if self._data.is_primary: self._send_new_view_if_needed() return vca = ViewChangeAck( viewNo=msg.viewNo, name=frm, digest=view_change_digest(msg) ) self._network.send(vca, self._data.primary_name) self._finish_view_change_if_needed() def process_view_change_ack_message(self, msg: ViewChangeAck, frm: str): result = self._validate(msg, frm) if result != PROCESS: return result if not self._data.is_primary: return self._votes.add_view_change_ack(msg, frm) self._send_new_view_if_needed() def process_new_view_message(self, msg: NewView, frm: str): result = self._validate(msg, frm) if result != PROCESS: return result self._new_view = msg self._finish_view_change_if_needed() def _validate(self, msg: Union[ViewChange, ViewChangeAck, NewView], frm: str) -> int: # TODO: Proper validation if msg.viewNo < self._data.view_no: return DISCARD if msg.viewNo == self._data.view_no and not self._data.waiting_for_new_view: return DISCARD if msg.viewNo > self._data.view_no: return STASH return PROCESS def _send_new_view_if_needed(self): confirmed_votes = self._votes.confirmed_votes if not self._data.quorums.view_change.is_reached(len(confirmed_votes)): return view_changes = [self._votes.get_view_change(*v) for v in confirmed_votes] cp = self._new_view_builder.calc_checkpoint(view_changes) if cp is None: return batches = self._new_view_builder.calc_batches(cp, view_changes) if batches is None: return nv = NewView( viewNo=self._data.view_no, viewChanges=confirmed_votes, checkpoint=cp, batches=batches ) self._network.send(nv) self._new_view = nv self._finish_view_change(cp, batches) def _finish_view_change_if_needed(self): if self._new_view is None: return view_changes = [] for name, vc_digest in self._new_view.viewChanges: vc = self._votes.get_view_change(name, vc_digest) # We don't have needed ViewChange, so we cannot validate NewView if vc is None: return view_changes.append(vc) cp = self._new_view_builder.calc_checkpoint(view_changes) if cp is None or cp != self._new_view.checkpoint: # New primary is malicious self.start_view_change() assert False # TODO: Test debugging purpose return batches = self._new_view_builder.calc_batches(cp, view_changes) if batches != self._new_view.batches: # New primary is malicious self.start_view_change() assert False # TODO: Test debugging purpose return self._finish_view_change(cp, batches) def _finish_view_change(self, cp: Checkpoint, batches: List[BatchID]): # Update checkpoint # TODO: change to self._bus.send(FinishViewChange(cp)) in scope of the task INDY-2179 self._data.stable_checkpoint = cp.seqNoEnd self._data.checkpoints = [old_cp for old_cp in self._data.checkpoints if old_cp.seqNoEnd > cp.seqNoEnd] self._data.checkpoints.append(cp) # Update batches # TODO: Actually we'll need to retrieve preprepares by ID from somewhere self._data.preprepared = batches # We finished a view change! self._data.waiting_for_new_view = False def _clear_old_batches(self, batches: Dict[int, Any]): for pp_seq_no in list(batches.keys()): if pp_seq_no <= self._data.stable_checkpoint: del batches[pp_seq_no]
def primary_selector(): return RoundRobinPrimariesSelector()
class ViewChangeService: def __init__(self, data: ConsensusSharedData, timer: TimerService, bus: InternalBus, network: ExternalBus, stasher: StashingRouter): self._config = getConfig() self._logger = getlogger() self._data = data self._new_view_builder = NewViewBuilder(self._data) self._timer = timer self._bus = bus self._network = network self._router = stasher self._votes = ViewChangeVotesForView(self._data.quorums) self._new_view = None # type: Optional[NewView] self._router.subscribe(ViewChange, self.process_view_change_message) self._router.subscribe(ViewChangeAck, self.process_view_change_ack_message) self._router.subscribe(NewView, self.process_new_view_message) self._old_prepared = {} # type: Dict[int, BatchID] self._old_preprepared = {} # type: Dict[int, List[BatchID]] self._primaries_selector = RoundRobinPrimariesSelector() self._subscription = Subscription() self._subscription.subscribe(self._bus, NeedViewChange, self.process_need_view_change) def __repr__(self): return self._data.name def process_need_view_change(self, msg: NeedViewChange): # 1. calculate new viewno view_no = msg.view_no if view_no is None: view_no = self._data.view_no + 1 # 2. Do cleanup before new view change starts self._clean_on_view_change_start() # 3. Update shared data self._data.view_no = view_no self._data.waiting_for_new_view = True self._data.primaries = self._primaries_selector.select_primaries( view_no=self._data.view_no, instance_count=self._data.quorums.f + 1, validators=self._data.validators) self._data.primary_name = self._data.primaries[self._data.inst_id] # 4. Build ViewChange message vc = self._build_view_change_msg() # 5. Send ViewChangeStarted via internal bus to update other services self._bus.send(ViewChangeStarted(view_no=self._data.view_no)) # 6. Send ViewChange msg to other nodes (via external bus) self._network.send(vc) self._votes.add_view_change(vc, self._data.name) # 6. Unstash messages for new view self._router.process_all_stashed() def _clean_on_view_change_start(self): self._clear_old_batches(self._old_prepared) self._clear_old_batches(self._old_preprepared) self._votes.clear() self._new_view = None def _clear_old_batches(self, batches: Dict[int, Any]): for pp_seq_no in list(batches.keys()): if pp_seq_no <= self._data.stable_checkpoint: del batches[pp_seq_no] def _build_view_change_msg(self): for batch_id in self._data.prepared: self._old_prepared[batch_id.pp_seq_no] = batch_id prepared = sorted([tuple(bid) for bid in self._old_prepared.values()]) for new_bid in self._data.preprepared: pretenders = self._old_preprepared.get(new_bid.pp_seq_no, []) pretenders = [ bid for bid in pretenders if bid.pp_digest != new_bid.pp_digest ] pretenders.append(new_bid) self._old_preprepared[new_bid.pp_seq_no] = pretenders preprepared = sorted([ tuple(bid) for bids in self._old_preprepared.values() for bid in bids ]) return ViewChange(viewNo=self._data.view_no, stableCheckpoint=self._data.stable_checkpoint, prepared=prepared, preprepared=preprepared, checkpoints=list(self._data.checkpoints)) def process_view_change_message(self, msg: ViewChange, frm: str): result = self._validate(msg, frm) if result != PROCESS: return result, None self._votes.add_view_change(msg, frm) if self._data.is_primary: self._send_new_view_if_needed() return PROCESS, None vca = ViewChangeAck(viewNo=msg.viewNo, name=frm, digest=view_change_digest(msg)) self._network.send(vca, self._data.primary_name) self._finish_view_change_if_needed() return PROCESS, None def process_view_change_ack_message(self, msg: ViewChangeAck, frm: str): result = self._validate(msg, frm) if result != PROCESS: return result, None if not self._data.is_primary: return PROCESS, None self._votes.add_view_change_ack(msg, frm) self._send_new_view_if_needed() return PROCESS, None def process_new_view_message(self, msg: NewView, frm: str): result = self._validate(msg, frm) if result != PROCESS: return result, None if frm != self._data.primary_name: self._logger.info( "{} Received NewView {} for view {} from non-primary {}; expected primary {}" .format(self._data.name, msg, self._data.view_no, frm, self._data.primary_name)) return DISCARD, "New View from non-Primary" self._new_view = msg self._finish_view_change_if_needed() return PROCESS, None def _validate(self, msg: Union[ViewChange, ViewChangeAck, NewView], frm: str) -> int: # TODO: Proper validation if msg.viewNo < self._data.view_no: return DISCARD if msg.viewNo == self._data.view_no and not self._data.waiting_for_new_view: return DISCARD if msg.viewNo > self._data.view_no: return STASH_VIEW return PROCESS def _send_new_view_if_needed(self): confirmed_votes = self._votes.confirmed_votes if not self._data.quorums.view_change.is_reached(len(confirmed_votes)): return view_changes = [ self._votes.get_view_change(*v) for v in confirmed_votes ] cp = self._new_view_builder.calc_checkpoint(view_changes) if cp is None: return batches = self._new_view_builder.calc_batches(cp, view_changes) if batches is None: return nv = NewView(viewNo=self._data.view_no, viewChanges=sorted(confirmed_votes, key=itemgetter(0)), checkpoint=cp, batches=batches) self._network.send(nv) self._new_view = nv self._finish_view_change() def _finish_view_change_if_needed(self): if self._new_view is None: return view_changes = [] for name, vc_digest in self._new_view.viewChanges: vc = self._votes.get_view_change(name, vc_digest) # We don't have needed ViewChange, so we cannot validate NewView if vc is None: return view_changes.append(vc) cp = self._new_view_builder.calc_checkpoint(view_changes) if cp is None or cp != self._new_view.checkpoint: # New primary is malicious self._logger.info( "{} Received invalid NewView {} for view {}: expected checkpoint {}" .format(self._data.name, self._new_view, self._data.view_no, cp)) self._bus.send(NeedViewChange()) return batches = self._new_view_builder.calc_batches(cp, view_changes) if batches != self._new_view.batches: # New primary is malicious self._logger.info( "{} Received invalid NewView {} for view {}: expected batches {}" .format(self._data.name, self._new_view, self._data.view_no, batches)) self._bus.send(NeedViewChange()) return self._finish_view_change() def _finish_view_change(self): # Update shared data self._data.waiting_for_new_view = False # send message to other services self._bus.send( NewViewAccepted(view_no=self._new_view.viewNo, view_changes=self._new_view.viewChanges, checkpoint=self._new_view.checkpoint, batches=self._new_view.batches))