From 947e1783e73043cc7a56340a6bc84865ddf3fc8d Mon Sep 17 00:00:00 2001 From: KickSeason Date: Tue, 11 Aug 2020 14:49:59 +0800 Subject: [PATCH 1/8] optimize LastSeenMessage --- src/neo/Consensus/ConsensusContext.cs | 22 ++++++++++--- src/neo/Consensus/ConsensusService.cs | 2 +- tests/neo.UnitTests/Consensus/UT_Consensus.cs | 32 +++++++++++++++---- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/neo/Consensus/ConsensusContext.cs b/src/neo/Consensus/ConsensusContext.cs index 763fa2e77f..1888148f34 100644 --- a/src/neo/Consensus/ConsensusContext.cs +++ b/src/neo/Consensus/ConsensusContext.cs @@ -35,7 +35,7 @@ internal class ConsensusContext : IDisposable, ISerializable public ConsensusPayload[] LastChangeViewPayloads; // LastSeenMessage array stores the height of the last seen message, for each validator. // if this node never heard from validator i, LastSeenMessage[i] will be -1. - public int[] LastSeenMessage; + public Dictionary LastSeenMessage { get; set; } /// /// Store all verified unsorted transactions' senders' fee currently in the consensus context. @@ -55,7 +55,7 @@ internal class ConsensusContext : IDisposable, ISerializable public bool WatchOnly => MyIndex < 0; public Header PrevHeader => Snapshot.GetHeader(Block.PrevHash); public int CountCommitted => CommitPayloads.Count(p => p != null); - public int CountFailed => LastSeenMessage.Count(p => p < (((int)Block.Index) - 1)); + public int CountFailed => LastSeenMessage.Count(p => p.Value < (((int)Block.Index) - 1)); #region Consensus States public bool RequestSentOrReceived => PreparationPayloads[Block.ConsensusData.PrimaryIndex] != null; @@ -386,9 +386,21 @@ public void Reset(byte viewNumber) CommitPayloads = new ConsensusPayload[Validators.Length]; if (LastSeenMessage == null) { - LastSeenMessage = new int[Validators.Length]; + LastSeenMessage = new Dictionary(); for (int i = 0; i < Validators.Length; i++) - LastSeenMessage[i] = -1; + LastSeenMessage[Validators[i]] = -1; + } + if (0 < Snapshot.Height && Blockchain.Singleton.GetBlock(Snapshot.Height - 1).NextConsensus != Blockchain.Singleton.GetBlock(Snapshot.Height).NextConsensus) + { + var previous_last_seen_message = LastSeenMessage; + LastSeenMessage = new Dictionary(); + foreach (var validator in Validators) + { + if (previous_last_seen_message.TryGetValue(validator, out int value)) + LastSeenMessage[validator] = value; + else + LastSeenMessage[validator] = -1; + } } keyPair = null; for (int i = 0; i < Validators.Length; i++) @@ -418,7 +430,7 @@ public void Reset(byte viewNumber) Block.Transactions = null; TransactionHashes = null; PreparationPayloads = new ConsensusPayload[Validators.Length]; - if (MyIndex >= 0) LastSeenMessage[MyIndex] = (int)Block.Index; + if (MyIndex >= 0) LastSeenMessage[Validators[MyIndex]] = (int)Block.Index; } public void Save() diff --git a/src/neo/Consensus/ConsensusService.cs b/src/neo/Consensus/ConsensusService.cs index 9caca4b651..fbcf0437c1 100644 --- a/src/neo/Consensus/ConsensusService.cs +++ b/src/neo/Consensus/ConsensusService.cs @@ -291,7 +291,7 @@ private void OnConsensusPayload(ConsensusPayload payload) { return; } - context.LastSeenMessage[payload.ValidatorIndex] = (int)payload.BlockIndex; + context.LastSeenMessage[context.Validators[payload.ValidatorIndex]] = (int)payload.BlockIndex; foreach (IP2PPlugin plugin in Plugin.P2PPlugins) if (!plugin.OnConsensusMessage(payload)) return; diff --git a/tests/neo.UnitTests/Consensus/UT_Consensus.cs b/tests/neo.UnitTests/Consensus/UT_Consensus.cs index 86eb6bc8c2..92fcaf8f32 100644 --- a/tests/neo.UnitTests/Consensus/UT_Consensus.cs +++ b/tests/neo.UnitTests/Consensus/UT_Consensus.cs @@ -46,7 +46,7 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm Console.WriteLine($"\n(UT-Consensus) Wallet is: {mockWallet.Object.GetAccount(UInt160.Zero).GetKey().PublicKey}"); var mockContext = new Mock(mockWallet.Object, Blockchain.Singleton.Store); - mockContext.Object.LastSeenMessage = new int[] { 0, 0, 0, 0, 0, 0, 0 }; + mockContext.Object.LastSeenMessage = new Dictionary(); KeyPair[] kp_array = new KeyPair[7] { @@ -136,6 +136,12 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm // As we may expect, as soon as consensus start it sends a RecoveryRequest of this aforementioned type var askingForInitialRecovery = subscriber.ExpectMsg(); Console.WriteLine($"Recovery Message I: {askingForInitialRecovery}"); + foreach (var validator in mockContext.Object.Validators) + { + Console.WriteLine("[test] {0}", validator); + mockContext.Object.LastSeenMessage[validator] = 0; + } + Console.WriteLine("[test] {0}", mockContext.Object.MyIndex); // Ensuring cast of type ConsensusPayload from the received message from subscriber ConsensusPayload initialRecoveryPayload = (ConsensusPayload)askingForInitialRecovery.Inventory; // Ensuring casting of type RecoveryRequest @@ -166,7 +172,11 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm mockContext.Object.ChangeViewPayloads[mockContext.Object.MyIndex] = null; Console.WriteLine("Forcing Failed nodes for recovery request... "); mockContext.Object.CountFailed.Should().Be(0); - mockContext.Object.LastSeenMessage = new int[] { -1, -1, -1, -1, -1, -1, -1 }; + mockContext.Object.LastSeenMessage = new Dictionary(); + foreach (var validator in mockContext.Object.Validators) + { + mockContext.Object.LastSeenMessage[validator] = -1; + } mockContext.Object.CountFailed.Should().Be(7); Console.WriteLine("\nWaiting for recovery due to failed nodes... "); var backupOnRecoveryDueToFailedNodes = subscriber.ExpectMsg(); @@ -266,6 +276,12 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm kp_array[6].PublicKey }; Console.WriteLine($"Generated keypairs PKey:"); + //refresh LastSeenMessage + mockContext.Object.LastSeenMessage = new Dictionary(); + foreach (var item in mockContext.Object.Validators) + { + mockContext.Object.LastSeenMessage[item] = -1; + } for (int i = 0; i < mockContext.Object.Validators.Length; i++) Console.WriteLine($"{mockContext.Object.Validators[i]}/{Contract.CreateSignatureContract(mockContext.Object.Validators[i]).ScriptHash}"); var updatedContract = Contract.CreateMultiSigContract(mockContext.Object.M, mockContext.Object.Validators); @@ -308,7 +324,7 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm Console.WriteLine("\nAsserting CountCommitted is 2..."); mockContext.Object.CountCommitted.Should().Be(2); Console.WriteLine($"\nAsserting CountFailed is 1..."); - mockContext.Object.CountFailed.Should().Be(1); + mockContext.Object.CountFailed.Should().Be(6); Console.WriteLine("\nCN6 simulation time"); TellConsensusPayload(actorConsensus, GetCommitPayloadModifiedAndSignedCopy(commitPayload, 5, kp_array[5], updatedBlockHashData)); @@ -318,7 +334,7 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm Console.WriteLine("\nAsserting CountCommitted is 3..."); mockContext.Object.CountCommitted.Should().Be(3); Console.WriteLine($"\nAsserting CountFailed is 0..."); - mockContext.Object.CountFailed.Should().Be(0); + mockContext.Object.CountFailed.Should().Be(5); Console.WriteLine("\nCN5 simulation time"); TellConsensusPayload(actorConsensus, GetCommitPayloadModifiedAndSignedCopy(commitPayload, 4, kp_array[4], updatedBlockHashData)); @@ -381,10 +397,14 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm Console.WriteLine("\nAsserting CountCommitted is 0..."); mockContext.Object.CountCommitted.Should().Be(0); Console.WriteLine($"\nAsserting CountFailed is 0..."); - mockContext.Object.CountFailed.Should().Be(0); + mockContext.Object.CountFailed.Should().Be(3); Console.WriteLine($"\nModifying CountFailed and asserting 7..."); // This will ensure a non-deterministic behavior after last recovery - mockContext.Object.LastSeenMessage = new int[] { -1, -1, -1, -1, -1, -1, -1 }; + mockContext.Object.LastSeenMessage = new Dictionary(); + foreach (var validator in mockContext.Object.Validators) + { + mockContext.Object.LastSeenMessage[validator] = -1; + } mockContext.Object.CountFailed.Should().Be(7); TellConsensusPayload(actorConsensus, rmPayload); From 5d8b4f013f6c1843867d9b83c0863ee17ae71d70 Mon Sep 17 00:00:00 2001 From: KickSeason Date: Tue, 11 Aug 2020 16:26:48 +0800 Subject: [PATCH 2/8] rm log --- tests/neo.UnitTests/Consensus/UT_Consensus.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/neo.UnitTests/Consensus/UT_Consensus.cs b/tests/neo.UnitTests/Consensus/UT_Consensus.cs index 92fcaf8f32..bbb23bea7f 100644 --- a/tests/neo.UnitTests/Consensus/UT_Consensus.cs +++ b/tests/neo.UnitTests/Consensus/UT_Consensus.cs @@ -138,10 +138,8 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm Console.WriteLine($"Recovery Message I: {askingForInitialRecovery}"); foreach (var validator in mockContext.Object.Validators) { - Console.WriteLine("[test] {0}", validator); mockContext.Object.LastSeenMessage[validator] = 0; } - Console.WriteLine("[test] {0}", mockContext.Object.MyIndex); // Ensuring cast of type ConsensusPayload from the received message from subscriber ConsensusPayload initialRecoveryPayload = (ConsensusPayload)askingForInitialRecovery.Inventory; // Ensuring casting of type RecoveryRequest From d3b3072fde815793d559293ba80e9ae8ac13410b Mon Sep 17 00:00:00 2001 From: KickSeason Date: Tue, 11 Aug 2020 18:47:22 +0800 Subject: [PATCH 3/8] use current height as default height --- src/neo/Consensus/ConsensusContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/Consensus/ConsensusContext.cs b/src/neo/Consensus/ConsensusContext.cs index 1888148f34..9facddcb8d 100644 --- a/src/neo/Consensus/ConsensusContext.cs +++ b/src/neo/Consensus/ConsensusContext.cs @@ -399,7 +399,7 @@ public void Reset(byte viewNumber) if (previous_last_seen_message.TryGetValue(validator, out int value)) LastSeenMessage[validator] = value; else - LastSeenMessage[validator] = -1; + LastSeenMessage[validator] = (int)Snapshot.Height; } } keyPair = null; From c0453256fa9a26726fb433110540dc66c651db6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vitor=20Naz=C3=A1rio=20Coelho?= Date: Tue, 11 Aug 2020 08:56:38 -0300 Subject: [PATCH 4/8] Update src/neo/Consensus/ConsensusContext.cs Co-authored-by: Shargon --- src/neo/Consensus/ConsensusContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/Consensus/ConsensusContext.cs b/src/neo/Consensus/ConsensusContext.cs index 9facddcb8d..cb56830dd3 100644 --- a/src/neo/Consensus/ConsensusContext.cs +++ b/src/neo/Consensus/ConsensusContext.cs @@ -388,7 +388,7 @@ public void Reset(byte viewNumber) { LastSeenMessage = new Dictionary(); for (int i = 0; i < Validators.Length; i++) - LastSeenMessage[Validators[i]] = -1; + LastSeenMessage[Validators[i]] = (int)Snapshot.Height; } if (0 < Snapshot.Height && Blockchain.Singleton.GetBlock(Snapshot.Height - 1).NextConsensus != Blockchain.Singleton.GetBlock(Snapshot.Height).NextConsensus) { From 6012922168eac75f20e5109f2073a3dafc837f75 Mon Sep 17 00:00:00 2001 From: zhangtao Date: Tue, 11 Aug 2020 20:41:13 +0800 Subject: [PATCH 5/8] clean --- src/neo/Consensus/ConsensusContext.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/neo/Consensus/ConsensusContext.cs b/src/neo/Consensus/ConsensusContext.cs index cb56830dd3..db878b7335 100644 --- a/src/neo/Consensus/ConsensusContext.cs +++ b/src/neo/Consensus/ConsensusContext.cs @@ -56,6 +56,7 @@ internal class ConsensusContext : IDisposable, ISerializable public Header PrevHeader => Snapshot.GetHeader(Block.PrevHash); public int CountCommitted => CommitPayloads.Count(p => p != null); public int CountFailed => LastSeenMessage.Count(p => p.Value < (((int)Block.Index) - 1)); + public bool ValidatorsChanged => 0 < Snapshot.Height && Blockchain.Singleton.GetBlock(Snapshot.Height - 1).NextConsensus != Blockchain.Singleton.GetBlock(Snapshot.Height).NextConsensus; #region Consensus States public bool RequestSentOrReceived => PreparationPayloads[Block.ConsensusData.PrimaryIndex] != null; @@ -390,7 +391,7 @@ public void Reset(byte viewNumber) for (int i = 0; i < Validators.Length; i++) LastSeenMessage[Validators[i]] = (int)Snapshot.Height; } - if (0 < Snapshot.Height && Blockchain.Singleton.GetBlock(Snapshot.Height - 1).NextConsensus != Blockchain.Singleton.GetBlock(Snapshot.Height).NextConsensus) + else if (ValidatorsChanged) { var previous_last_seen_message = LastSeenMessage; LastSeenMessage = new Dictionary(); From 59ae72d5ba6ca355e6a3d9acd143005fc0771f93 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Wed, 12 Aug 2020 14:49:04 +0800 Subject: [PATCH 6/8] Update ConsensusContext.cs --- src/neo/Consensus/ConsensusContext.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/neo/Consensus/ConsensusContext.cs b/src/neo/Consensus/ConsensusContext.cs index db878b7335..f1edd0ebe7 100644 --- a/src/neo/Consensus/ConsensusContext.cs +++ b/src/neo/Consensus/ConsensusContext.cs @@ -35,7 +35,7 @@ internal class ConsensusContext : IDisposable, ISerializable public ConsensusPayload[] LastChangeViewPayloads; // LastSeenMessage array stores the height of the last seen message, for each validator. // if this node never heard from validator i, LastSeenMessage[i] will be -1. - public Dictionary LastSeenMessage { get; set; } + public Dictionary LastSeenMessage { get; private set; } /// /// Store all verified unsorted transactions' senders' fee currently in the consensus context. @@ -55,7 +55,7 @@ internal class ConsensusContext : IDisposable, ISerializable public bool WatchOnly => MyIndex < 0; public Header PrevHeader => Snapshot.GetHeader(Block.PrevHash); public int CountCommitted => CommitPayloads.Count(p => p != null); - public int CountFailed => LastSeenMessage.Count(p => p.Value < (((int)Block.Index) - 1)); + public int CountFailed => LastSeenMessage?.Count(p => p.Value < (((int)Block.Index) - 1)) ?? 0; public bool ValidatorsChanged => 0 < Snapshot.Height && Blockchain.Singleton.GetBlock(Snapshot.Height - 1).NextConsensus != Blockchain.Singleton.GetBlock(Snapshot.Height).NextConsensus; #region Consensus States @@ -385,19 +385,13 @@ public void Reset(byte viewNumber) ChangeViewPayloads = new ConsensusPayload[Validators.Length]; LastChangeViewPayloads = new ConsensusPayload[Validators.Length]; CommitPayloads = new ConsensusPayload[Validators.Length]; - if (LastSeenMessage == null) - { - LastSeenMessage = new Dictionary(); - for (int i = 0; i < Validators.Length; i++) - LastSeenMessage[Validators[i]] = (int)Snapshot.Height; - } - else if (ValidatorsChanged) + if (ValidatorsChanged || LastSeenMessage is null) { var previous_last_seen_message = LastSeenMessage; LastSeenMessage = new Dictionary(); foreach (var validator in Validators) { - if (previous_last_seen_message.TryGetValue(validator, out int value)) + if (previous_last_seen_message != null && previous_last_seen_message.TryGetValue(validator, out int value)) LastSeenMessage[validator] = value; else LastSeenMessage[validator] = (int)Snapshot.Height; From a7958df8c961236bcc0903a70135da19bfe69a75 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Wed, 12 Aug 2020 14:56:55 +0800 Subject: [PATCH 7/8] Optimize --- src/neo/Consensus/ConsensusContext.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/neo/Consensus/ConsensusContext.cs b/src/neo/Consensus/ConsensusContext.cs index f1edd0ebe7..b968f4dc47 100644 --- a/src/neo/Consensus/ConsensusContext.cs +++ b/src/neo/Consensus/ConsensusContext.cs @@ -56,7 +56,16 @@ internal class ConsensusContext : IDisposable, ISerializable public Header PrevHeader => Snapshot.GetHeader(Block.PrevHash); public int CountCommitted => CommitPayloads.Count(p => p != null); public int CountFailed => LastSeenMessage?.Count(p => p.Value < (((int)Block.Index) - 1)) ?? 0; - public bool ValidatorsChanged => 0 < Snapshot.Height && Blockchain.Singleton.GetBlock(Snapshot.Height - 1).NextConsensus != Blockchain.Singleton.GetBlock(Snapshot.Height).NextConsensus; + public bool ValidatorsChanged + { + get + { + if (Snapshot.Height == 0) return false; + TrimmedBlock currentBlock = Snapshot.Blocks[Snapshot.CurrentBlockHash]; + TrimmedBlock previousBlock = Snapshot.Blocks[currentBlock.PrevHash]; + return currentBlock.NextConsensus != previousBlock.NextConsensus; + } + } #region Consensus States public bool RequestSentOrReceived => PreparationPayloads[Block.ConsensusData.PrimaryIndex] != null; From 5663d5c1f3ebc0f0ebe286c68186e7a0d0d90f28 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Wed, 12 Aug 2020 15:01:39 +0800 Subject: [PATCH 8/8] Fix --- tests/neo.UnitTests/Consensus/UT_Consensus.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/neo.UnitTests/Consensus/UT_Consensus.cs b/tests/neo.UnitTests/Consensus/UT_Consensus.cs index bbb23bea7f..5bcf9f031c 100644 --- a/tests/neo.UnitTests/Consensus/UT_Consensus.cs +++ b/tests/neo.UnitTests/Consensus/UT_Consensus.cs @@ -46,7 +46,6 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm Console.WriteLine($"\n(UT-Consensus) Wallet is: {mockWallet.Object.GetAccount(UInt160.Zero).GetKey().PublicKey}"); var mockContext = new Mock(mockWallet.Object, Blockchain.Singleton.Store); - mockContext.Object.LastSeenMessage = new Dictionary(); KeyPair[] kp_array = new KeyPair[7] { @@ -170,7 +169,7 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm mockContext.Object.ChangeViewPayloads[mockContext.Object.MyIndex] = null; Console.WriteLine("Forcing Failed nodes for recovery request... "); mockContext.Object.CountFailed.Should().Be(0); - mockContext.Object.LastSeenMessage = new Dictionary(); + mockContext.Object.LastSeenMessage.Clear(); foreach (var validator in mockContext.Object.Validators) { mockContext.Object.LastSeenMessage[validator] = -1; @@ -275,7 +274,7 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm }; Console.WriteLine($"Generated keypairs PKey:"); //refresh LastSeenMessage - mockContext.Object.LastSeenMessage = new Dictionary(); + mockContext.Object.LastSeenMessage.Clear(); foreach (var item in mockContext.Object.Validators) { mockContext.Object.LastSeenMessage[item] = -1; @@ -398,7 +397,7 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm mockContext.Object.CountFailed.Should().Be(3); Console.WriteLine($"\nModifying CountFailed and asserting 7..."); // This will ensure a non-deterministic behavior after last recovery - mockContext.Object.LastSeenMessage = new Dictionary(); + mockContext.Object.LastSeenMessage.Clear(); foreach (var validator in mockContext.Object.Validators) { mockContext.Object.LastSeenMessage[validator] = -1;