Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
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]
Ejemplo n.º 7
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))
Ejemplo n.º 8
0
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]
Ejemplo n.º 9
0
def primary_selector():
    return RoundRobinPrimariesSelector()
Ejemplo n.º 10
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
        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))