Пример #1
0
        public async Task <SendLeaderCommandResponse> Receive(ICommand command)
        {
            if (State is Follower)
            {
                await _messageBus.Send(command, LeaderId);
            }

            if (State is Leader)
            {
                _logger.LogDebug("Server Received Command");
                _appendingEntries = true;
                Log.Add(new Log(CurrentTerm, command));
                CommitIndex = Log.Count - 1;

                var remoteServers = GetRemoteServers();

                var tasks = new Task <AppendEntriesResponse> [remoteServers.Count];

                for (int i = 0; i < tasks.Length; i++)
                {
                    var next = NextIndex.FirstOrDefault(x => x.Id == remoteServers[i].Id);
                    if (next == null)
                    {
                        var nextLogIndex = 0;
                        next = new Next(remoteServers[i].Id, nextLogIndex);
                        NextIndex.Add(next);
                    }
                    var match = MatchIndex.FirstOrDefault(x => x.Id == remoteServers[i].Id);
                    if (match == null)
                    {
                        match = new Match(remoteServers[i].Id, 0);
                        MatchIndex.Add(match);
                    }
                    var lastLogIndex = Log.Count > 0 ? Log.Count - 1 : 0;
                    var lastLogTerm  = lastLogIndex > 0 ? Log[match.MatchIndex].Term : 0;

                    // If last log index ≥ nextIndex for a follower: send
                    // AppendEntries RPC with log entries starting at nextIndex
                    if (lastLogIndex >= next.NextIndex)
                    {
                        var log           = Log[next.NextIndex];
                        var appendEntries = new AppendEntries(CurrentTerm, Id, match.MatchIndex, lastLogTerm, log, CommitIndex, remoteServers[i].Id);
                        tasks[i] = _messageBus.Send(appendEntries);
                    }
                }

                Task.WaitAll(tasks);
                int counter = 0;
                foreach (var task in tasks)
                {
                    _logger.LogDebug($"Processing Append entries counter: {counter}");
                    _logger.LogDebug($"Processing Append entries result was: {task.Result.Success} counter: {counter}");
                    await Receive(task.Result);

                    _logger.LogDebug($"Processed Append entries counter: {counter}");
                }
            }

            return(new SendLeaderCommandResponse());
        }
Пример #2
0
        public AppendEntriesResponse Request(AppendEntries appendEntries)
        {
            try
            {
                if (_token == null)
                {
                    SetToken();
                }

                var json    = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings);
                var content = new StringContent(json);
                content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
                var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content).GetAwaiter().GetResult();
                if (response.IsSuccessStatusCode)
                {
                    return(JsonConvert.DeserializeObject <AppendEntriesResponse>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings));
                }
                else
                {
                    return(new AppendEntriesResponse(appendEntries.Term, false));
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                return(new AppendEntriesResponse(appendEntries.Term, false));
            }
        }
Пример #3
0
        public void Receive(SendHeartbeat sendHeartbeat)
        {
            if (State is Leader)
            {
                var remoteServers = GetRemoteServers();

                var tasks = new Task <AppendEntriesResponse> [remoteServers.Count];
                for (int i = 0; i < tasks.Length; i++)
                {
                    var lastLogIndex  = Log.Count > 0 ? Log.Count - 1 : 0;
                    var lastLogTerm   = lastLogIndex > 0 ? Log[lastLogIndex].Term : 0;
                    var appendEntries = new AppendEntries(CurrentTerm, Id, lastLogIndex, lastLogTerm, null, CommitIndex, remoteServers[i].Id);
                    tasks[i] = _messageBus.Send(appendEntries);
                }

                Task.WaitAll(tasks);

                foreach (var task in tasks)
                {
                    if (task.Result.Term > CurrentTerm)
                    {
                        BecomeFollowerAndMatchTerm(task.Result.Term, task.Result.FollowerId);
                    }
                }

                _messageBus.Publish(new SendToSelf(new SendHeartbeat()));
            }
        }
Пример #4
0
        public void NodeStaysAFollowerWhenReceiveAppendEntries()
        {
            using (Helpers.InitLog4Net())
            {
                var settings = Helpers.BuildNodeSettings("1", new[] { "1", "2", "3", "4", "5" });
                settings.TimeoutInMs = 20;
                var middleware = new Middleware();
                var node       = new Node <string>(TestHelpers.GetPool().BuildSequencer(), settings, middleware, new StateMachine());

                using (node)
                {
                    node.Initialize();

                    // should switch to candidate
                    Check.That(this.WaitState(node, NodeStatus.Candidate, 40)).IsTrue();

                    // now we pretend there is a leader
                    var message = new AppendEntries <string>
                    {
                        LeaderId     = "2",
                        LeaderTerm   = 5,
                        PrevLogIndex = -1,
                        PrevLogTerm  = 0
                    };

                    var entry = new LogEntry <string>("dummy", 1L);
                    message.Entries = new[] { entry };
                    middleware.SendMessage("1", message);
                    Check.That(this.WaitState(node, NodeStatus.Follower, 30)).IsTrue();

                    Check.That(node.State.LogEntries.Count).IsEqualTo(1);
                }
            }
        }
Пример #5
0
        public async Task <AppendEntriesResponse> Send(AppendEntries appendEntries)
        {
            try
            {
                var serverToSendMessageTo = _serviceRegistry.Get(RaftyServiceDiscoveryName.Get()).First(x => x.Id == appendEntries.FollowerId);
                var json = JsonConvert.SerializeObject(appendEntries, Formatting.None, new JsonSerializerSettings
                {
                    TypeNameHandling = TypeNameHandling.All
                });
                var httpContent = new StringContent(json);
                httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

                using (var httpClient = new HttpClient())
                {
                    httpClient.BaseAddress = serverToSendMessageTo.Location;
                    var response = await httpClient.PostAsync(_appendEntriesUrl, httpContent);

                    response.EnsureSuccessStatusCode();
                    var content = await response.Content.ReadAsStringAsync();

                    var appendEntriesResponse = JsonConvert.DeserializeObject <AppendEntriesResponse>(content);
                    return(appendEntriesResponse);
                }
            }
            catch (Exception exception)
            {
                _logger.LogError(new EventId(1), exception, "Error in Send(AppendEntries appendEntries)");
                throw;
            }
        }
Пример #6
0
        private void GivenTheCandidatesLogIsAtIndex(int index)
        {
            var entries = new Log(1, new FakeCommand(Guid.NewGuid()));

            var appendEntries = new AppendEntries(1, Guid.NewGuid(), index, 0, entries, 0, Guid.NewGuid());

            _server.Receive(appendEntries).Wait();
        }
Пример #7
0
 public async Task <AppendEntriesResponse> Request(AppendEntries appendEntries)
 {
     if (appendEntries.Entries.Count > 0)
     {
         AppendEntriesResponsesWithLogEntries++;
     }
     AppendEntriesResponses++;
     return(_appendEntriesResponse);
 }
Пример #8
0
        public void should_add_remote_server_if_receive_append_entries_from_unknown_remote_server()
        {
            var appendEntries = new AppendEntries(20, Guid.NewGuid(), 0, 0, null, 0, Guid.NewGuid());

            this.Given(x => GivenANewServer())
            .When(x => ServerReceives(appendEntries))
            .Then(x => TheRemoteServerCountIs(1))
            .BDDfy();
        }
Пример #9
0
 public Task <AppendEntriesResponse> Handle(AppendEntries appendEntries)
 {
     //follower不对外输出日志
     return(Task.FromResult(new AppendEntriesResponse()
     {
         Success = false,
         Term = CurrentState.CurrentTerm
     }));
 }
Пример #10
0
        public void server_should_become_follower_if_receives_greater_term_in_append_entries_message()
        {
            var appendEntries = new AppendEntries(20, Guid.NewGuid(), 0, 0, null, 0, Guid.NewGuid());

            this.Given(x => GivenANewServer())
            .When(x => ServerReceives(appendEntries))
            .Then(x => ThenTheCurrentTermIs(20))
            .And(x => TheServerIsAFollower())
            .BDDfy();
        }
Пример #11
0
        public async Task <AppendEntriesResponse> Handle(AppendEntries appendEntries)
        {
            var response = await State.Handle(appendEntries);

            if (response.BlockEntity != null)
            {
                _logger.LogInformation($"{State.GetType().Name} id: {State.CurrentState.Id} responded to appendentries with success: {response.Success} and term: {response.Term}");
            }
            return(response);
        }
Пример #12
0
        private void AppendEntriesTermIsGreaterThanCurrentTerm(AppendEntries appendEntries)
        {
            if (appendEntries.Term > CurrentState.CurrentTerm)
            {
                CurrentState = new CurrentState(CurrentState.Id, appendEntries.Term, CurrentState.VotedFor,
                                                CurrentState.CommitIndex, CurrentState.LastApplied, CurrentState.LeaderId);

                BecomeFollower();
            }
        }
Пример #13
0
        //追加 peer
        public async Task <AppendEntriesResponse> Handle(AppendEntries appendEntries)
        {
            var node = _nodePeer.GetNode(appendEntries.ChannelId);

            if (node == null)
            {
                throw new Exception($"节点未加入{appendEntries.ChannelId} 通道");
            }
            return(await node.Handle(appendEntries));
        }
Пример #14
0
 public async Task <AppendEntriesResponse> Request(AppendEntries appendEntries)
 {
     try
     {
         return(await _node.Handle(appendEntries));
     }
     catch (Exception e)
     {
         return(new AppendEntriesResponse(0, false));
     }
 }
Пример #15
0
        public void server_should_become_follower_if_receives_append_entries_while_candidate()
        {
            var appendEntries = new AppendEntries(0, Guid.NewGuid(), 0, 0, null, 0, Guid.NewGuid());

            this.Given(x => GivenANewServer())
            .And(x => ServerReceives(new BecomeCandidate(Guid.NewGuid())))
            .When(x => ServerReceives(appendEntries))
            .Then(x => ThenTheServerIsAFollower())
            .And(x => ThenTheCurrentTermVotesAre(0))
            .BDDfy();
        }
Пример #16
0
 public AppendEntriesResponse Request(AppendEntries appendEntries)
 {
     try
     {
         return(_node.Handle(appendEntries));
     }
     catch (Exception e)
     {
         return(new AppendEntriesResponse(0, false));
     }
 }
Пример #17
0
        public void server_should_set_current_term_as_message_term_if_greater_than_current_term()
        {
            var id            = Guid.NewGuid();
            var appendEntries = new AppendEntries(1, id, 0, 0, null, 0, Guid.NewGuid());
            var expected      = new AppendEntriesResponse(1, true, id, Guid.NewGuid());

            this.Given(x => GivenANewServer())
            .When(x => ServerReceives(appendEntries))
            .Then(x => ThenTheReplyIs(expected))
            .And(x => ThenTheCurrentTermIs(1))
            .BDDfy();
        }
Пример #18
0
        public void server_should_reply_true_if_entries_is_empty()
        {
            var id            = Guid.NewGuid();
            var appendEntries = new AppendEntries(0, id, 0, 0, null, 0, Guid.NewGuid());
            var expected      = new AppendEntriesResponse(0, true, id, Guid.NewGuid());

            this.Given(x => GivenANewServer())
            .When(x => ServerReceives(appendEntries))
            .Then(x => ThenTheReplyIs(expected))
            .And(x => ThenTheCurrentTermIs(0))
            .BDDfy();
        }
Пример #19
0
        public void server_should_append_new_entries_not_in_log_and_reply_true()
        {
            var entries       = new Log(0, new FakeCommand(Guid.NewGuid()));
            var id            = Guid.NewGuid();
            var appendEntries = new AppendEntries(0, id, 1, 0, entries, 0, Guid.NewGuid());
            var expected      = new AppendEntriesResponse(0, true, id, Guid.NewGuid());

            this.Given(x => GivenANewServer())
            .When(x => ServerReceives(appendEntries))
            .Then(x => ThenTheReplyIs(expected))
            .And(x => ThenTheLogContainsEntriesCount(1))
            .BDDfy();
        }
Пример #20
0
        public void server_should_reply_false_if_term_is_less_than_current_term()
        {
            var entries       = new Log(1, new FakeCommand(Guid.NewGuid()));
            var id            = Guid.NewGuid();
            var appendEntries = new AppendEntries(0, id, 0, 0, entries, 0, Guid.NewGuid());
            var expected      = new AppendEntriesResponse(1, false, id, Guid.NewGuid());

            this.Given(x => GivenANewServer())
            .And(x => GivenTheCurrentTermIs(1))
            .When(x => ServerReceives(appendEntries))
            .Then(x => ThenTheReplyIs(expected))
            .BDDfy();
        }
Пример #21
0
        public void should_set_commit_index_if_leader_commit_greater_than_commit_index()
        {
            var entries       = new Log(0, new FakeCommand(Guid.NewGuid()));
            var id            = Guid.NewGuid();
            var appendEntries = new AppendEntries(0, id, 1, 0, entries, 1, Guid.NewGuid());
            var expected      = new AppendEntriesResponse(0, true, id, Guid.NewGuid());

            this.Given(x => GivenANewServer())
            .When(x => ServerReceives(appendEntries))
            .Then(x => ThenTheReplyIs(expected))
            .And(x => ThenTheCommitIndexIs(1))
            .BDDfy();
        }
Пример #22
0
        public async Task <AppendEntriesResponse> Handle(AppendEntries appendEntries)
        {
            try
            {
                await _appendingEntries.WaitAsync();

                var response = _rules.AppendEntriesTermIsLessThanCurrentTerm(appendEntries, CurrentState);

                if (response.shouldReturn)
                {
                    return(response.appendEntriesResponse);
                }

                response =
                    await _rules.LogDoesntContainEntryAtPreviousLogIndexWhoseTermMatchesPreviousLogTerm(appendEntries,
                                                                                                        _log, CurrentState);

                if (response.shouldReturn)
                {
                    return(response.appendEntriesResponse);
                }

                await _rules.DeleteAnyConflictsInLog(appendEntries, _log);

                var terms = appendEntries.Entries.Any()
                    ? string.Join(",", appendEntries.Entries.Select(x => x.Term))
                    : string.Empty;

                _logger.LogInformation(
                    $"{CurrentState.Id} as {nameof(Follower)} applying {appendEntries.Entries.Count} to log, term {terms}");

                await _rules.ApplyNewEntriesToLog(appendEntries, _log);

                var commitIndexAndLastApplied =
                    await _rules.CommitIndexAndLastApplied(appendEntries, _log, CurrentState);

                await ApplyToStateMachine(commitIndexAndLastApplied.commitIndex, commitIndexAndLastApplied.lastApplied,
                                          appendEntries);

                SetLeaderId(appendEntries);

                _messagesSinceLastElectionExpiry++;

                return(new AppendEntriesResponse(CurrentState.CurrentTerm, true));
            }
            finally
            {
                _appendingEntries.Release();
            }
        }
Пример #23
0
 internal override void ProcessAppendEntries(AppendEntries <T> appendEntries)
 {
     if (appendEntries.LeaderTerm >= this.CurrentTerm)
     {
         this.Logger.InfoFormat(
             "Received AppendEntries from a probable leader, stepping down ({0}).",
             appendEntries);
         this.Node.SwitchToAndProcessMessage(NodeStatus.Follower, appendEntries);
     }
     else
     {
         Logger.Debug("Received AppendEntries from an invalid leader, refusing.");
         var reply = new AppendEntriesAck(this.Node.Id, this.CurrentTerm, false);
         this.Node.SendMessage(appendEntries.LeaderId, reply);
     }
 }
Пример #24
0
        public async Task <AppendEntriesResponse> Handle(AppendEntries appendEntries)
        {
            if (appendEntries.Term > CurrentState.CurrentTerm)
            {
                var response = await _rules.CommitIndexAndLastApplied(appendEntries, _log, CurrentState);

                await ApplyToStateMachine(appendEntries, response.commitIndex, response.lastApplied);

                SetLeaderId(appendEntries);

                _node.BecomeFollower(CurrentState);

                return(new AppendEntriesResponse(CurrentState.CurrentTerm, true));
            }

            return(new AppendEntriesResponse(CurrentState.CurrentTerm, false));
        }
Пример #25
0
        internal override void ProcessAppendEntries(AppendEntries <T> appendEntries)
        {
            bool result;

            if (appendEntries.LeaderTerm < this.CurrentTerm)
            {
                // leader is older than us or log does not match
                this.Logger.DebugFormat(
                    "Reject an AppendEntries from an invalid leader ({0}).", appendEntries);
                result = false;
            }
            else
            {
                // we will proceed
                this.Node.LeaderId = appendEntries.LeaderId;
                if (appendEntries.LeaderTerm > this.CurrentTerm)
                {
                    this.Logger.TraceFormat("Upgrade our term to {0}.", this.CurrentTerm);
                    this.Node.State.CurrentTerm = appendEntries.LeaderTerm;
                }

                if (this.Node.State.EntryMatches(
                        appendEntries.PrevLogIndex, appendEntries.PrevLogTerm))
                {
                    this.Logger.TraceFormat(
                        "Process an AppendEntries request: {0}", appendEntries);
                    this.Node.State.AppendEntries(appendEntries.PrevLogIndex, appendEntries.Entries);
                    this.Node.Commit(appendEntries.CommitIndex);
                    result = true;
                }
                else
                {
                    // log does not match, we are not in sync with leader yet
                    this.Logger.DebugFormat(
                        "Reject an AppendEntries that does not match our log ({0}).",
                        appendEntries);
                    result = false;
                }
            }

            var reply = new AppendEntriesAck(this.Node.Id, this.CurrentTerm, result);

            this.Node.SendMessage(appendEntries.LeaderId, reply);
            this.ResetTimeout(.2);
        }
Пример #26
0
        server_should_reply_false_if_log_doesnt_contain_an_entry_at_previous_log_index_matching_previous_log_term()
        {
            var entries = new Log(0, new FakeCommand(Guid.NewGuid()));

            var id = Guid.NewGuid();

            var appendEntries = new AppendEntries(0, id, 0, 0, entries, 0, Guid.NewGuid());

            var oldAppendEntries = new AppendEntries(0, id, 0, 1, entries, 0, Guid.NewGuid());

            var expected = new AppendEntriesResponse(0, false, id, Guid.NewGuid());

            this.Given(x => GivenANewServer())
            .And(x => GivenTheServerRecieves(appendEntries))
            .When(x => ServerReceives(oldAppendEntries))
            .Then(x => ThenTheReplyIs(expected))
            .BDDfy();
        }
Пример #27
0
        public void Send(JsonOperationContext context, Action updateFollowerTicks, AppendEntries ae, List <BlittableJsonReaderObject> items = null)
        {
            if (_log.IsInfoEnabled)
            {
                if (ae.EntriesCount > 0)
                {
                    _log.Info(
                        $"AppendEntries ({ae.EntriesCount:#,#;;0}) in {ae.Term:#,#;;0}, commit: {ae.LeaderCommit:#,#;;0}, leader for: {ae.TimeAsLeader:#,#;;0}, ({ae.PrevLogIndex:#,#;;0} / {ae.PrevLogTerm:#,#;;0}), truncate: {ae.TruncateLogBefore:#,#;;0}, force elections: {ae.ForceElections}.");
                }
            }

            var msg = new DynamicJsonValue
            {
                ["Type"] = nameof(AppendEntries),
                [nameof(AppendEntries.EntriesCount)]      = ae.EntriesCount,
                [nameof(AppendEntries.LeaderCommit)]      = ae.LeaderCommit,
                [nameof(AppendEntries.PrevLogIndex)]      = ae.PrevLogIndex,
                [nameof(AppendEntries.PrevLogTerm)]       = ae.PrevLogTerm,
                [nameof(AppendEntries.Term)]              = ae.Term,
                [nameof(AppendEntries.TruncateLogBefore)] = ae.TruncateLogBefore,
                [nameof(AppendEntries.TimeAsLeader)]      = ae.TimeAsLeader,
                [nameof(AppendEntries.SendingThread)]     = Thread.CurrentThread.ManagedThreadId,
                [nameof(AppendEntries.MinCommandVersion)] = ae.MinCommandVersion
            };

            if (ae.ForceElections)
            {
                msg[nameof(AppendEntries.ForceElections)] = true;
            }

            Send(context, msg);

            if (items == null || items.Count == 0)
            {
                return;
            }

            foreach (var item in items)
            {
                updateFollowerTicks();
                Send(context, item);
            }
        }
Пример #28
0
        public async Task <AppendEntriesResponse> Handle(AppendEntries appendEntries)
        {
            try
            {
                await _appendingEntries.WaitAsync();

                var response = await State.Handle(appendEntries);

                if (appendEntries.Entries.Any())
                {
                    _logger.LogInformation($"{State.GetType().Name} id: {State.CurrentState.Id} responded to appendentries with success: {response.Success} and term: {response.Term}");
                }

                return(response);
            }
            finally
            {
                _appendingEntries.Release();
            }
        }
Пример #29
0
        private async Task ApplyToStateMachine(int commitIndex, int lastApplied, AppendEntries appendEntries)
        {
            while (commitIndex > lastApplied)
            {
                _logger.LogInformation($"id: {CurrentState.Id} handling log in fsm in loop, commitIndex: {commitIndex}, lastApplied: {lastApplied}, ae.Count: {appendEntries.Entries.Count}");

                lastApplied++;

                var log = await _log.Get(lastApplied);

                await _fsm.Handle(log);
            }

            CurrentState = new CurrentState(CurrentState.Id, appendEntries.Term,
                                            CurrentState.VotedFor, commitIndex, lastApplied, CurrentState.LeaderId);

            _logger.LogInformation($"id: {CurrentState.Id} handling log in fsm out of loop now, commitIndex: {commitIndex}, lastApplied: {lastApplied}, ae.Count: {appendEntries.Entries.Count}");

            _logger.LogInformation($"id: {CurrentState.Id} CurrentState.CommitIndex: {CurrentState.CommitIndex}, CurrentState.LastApplied: {CurrentState.LastApplied}");
        }
Пример #30
0
        private void BecomeLeader()
        {
            if (State is Candidate)
            {
                State    = new Leader();
                LeaderId = Id;
                _messageBus.Publish(new SendToSelf(new SendHeartbeat()));
                NextIndex  = new List <Next>();
                MatchIndex = new List <Match>();

                var remoteServers = GetRemoteServers();

                for (var i = 0; i < remoteServers.Count; i++)
                {
                    var nextLogIndex = Log.Count;
                    var next         = new Next(remoteServers[i].Id, nextLogIndex);
                    NextIndex.Add(next);
                    var match = new Match(remoteServers[i].Id, 0);
                    MatchIndex.Add(match);
                }

                var tasks = new Task <AppendEntriesResponse> [remoteServers.Count];
                for (var i = 0; i < tasks.Length; i++)
                {
                    var lastLogIndex  = Log.Count > 0 ? Log.Count - 1 : 0;
                    var lastLogTerm   = lastLogIndex > 0 ? Log[lastLogIndex].Term : 0;
                    var appendEntries = new AppendEntries(CurrentTerm, Id, lastLogIndex, lastLogTerm, null, CommitIndex, remoteServers[i].Id);
                    tasks[i] = _messageBus.Send(appendEntries);
                }

                Task.WaitAll(tasks);

                foreach (var task in tasks)
                {
                    if (task.Result.Term > CurrentTerm)
                    {
                        BecomeFollowerAndMatchTerm(task.Result.Term, task.Result.FollowerId);
                    }
                }
            }
        }
Пример #31
0
        public void NodeStaysAFollowerWhenReceiveAppendEntries()
        {
            using (Helpers.InitLog4Net())
            {
                var settings = Helpers.BuildNodeSettings("1", new[] { "1", "2", "3", "4", "5" });
                settings.TimeoutInMs = 20;
                var middleware = new Middleware();
                var node = new Node<string>(TestHelpers.GetPool().BuildSequencer(), settings, middleware, new StateMachine());

                using (node)
                {
                    node.Initialize();

                    // should switch to candidate
                    Check.That(this.WaitState(node, NodeStatus.Candidate, 40)).IsTrue();

                    // now we pretend there is a leader
                    var message = new AppendEntries<string>
                                      {
                                          LeaderId = "2",
                                          LeaderTerm = 5,
                                          PrevLogIndex = -1,
                                          PrevLogTerm = 0
                                      };

                    var entry = new LogEntry<string>("dummy", 1L);
                    message.Entries = new[] { entry };
                    middleware.SendMessage("1", message);
                    Check.That(this.WaitState(node, NodeStatus.Follower, 30)).IsTrue();

                    Check.That(node.State.LogEntries.Count).IsEqualTo(1);
                }
            }
        }