diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..136d75897 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0.101-noble + +# Install required dependencies for LevelDB and SQLite +RUN apt-get update && \ + apt-get install -y \ + libleveldb-dev \ + sqlite3 \ + libsqlite3-dev \ + librocksdb-dev \ + libsnappy-dev \ + libunwind8-dev \ + && rm -rf /var/lib/apt/lists/* + diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..a147d6c60 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,21 @@ +{ + "name": "Neo Node", + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind", + "features": {}, + "customizations": { + "vscode": { + "extensions": [ + "ms-dotnettools.csharp", + "ms-dotnettools.csdevkit" + ] + } + }, + "remoteUser": "root", + "postCreateCommand": "dotnet restore" +} + diff --git a/plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs b/plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs index 2c3062765..1420969a2 100644 --- a/plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs +++ b/plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs @@ -46,6 +46,19 @@ private RecoveryMessage.ChangeViewPayloadCompact GetChangeViewPayloadCompact(Ext }; } + + private RecoveryMessage.PreCommitPayloadCompact GetPreCommitPayloadCompact(ExtensiblePayload payload) + { + PreCommit preCommit = GetMessage(payload); + return new RecoveryMessage.PreCommitPayloadCompact + { + ViewNumber = preCommit.ViewNumber, + ValidatorIndex = preCommit.ValidatorIndex, + PreparationHash = preCommit.PreparationHash, + InvocationScript = payload.Witness.InvocationScript, + }; + } + private RecoveryMessage.CommitPayloadCompact GetCommitPayloadCompact(ExtensiblePayload payload) { Commit message = GetMessage(payload); @@ -68,12 +81,21 @@ private RecoveryMessage.PreparationPayloadCompact GetPreparationPayloadCompact(E } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetPrimaryIndex(byte viewNumber) + public byte GetPriorityPrimaryIndex(byte viewNumber) { - int p = ((int)Block.Index - viewNumber) % Validators.Length; + int p = ((int)Block[0].Index - viewNumber) % Validators.Length; return p >= 0 ? (byte)p : (byte)(p + Validators.Length); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetFallbackPrimaryIndex(byte priorityPrimaryIndex) + { + if (Validators.Length <= 1) return priorityPrimaryIndex; + int p = ((int)Block[0].Index + 1) % (Validators.Length - 1); + p = p >= 0 ? (byte)p : (byte)(p + Validators.Length); + return p < priorityPrimaryIndex ? (byte)p : (byte)(p + 1); + } + public UInt160 GetSender(int index) { return Contract.CreateSignatureRedeemScript(Validators[index]).ToScriptHash(); @@ -82,18 +104,19 @@ public UInt160 GetSender(int index) /// /// Return the expected block size /// - public int GetExpectedBlockSize() + public int GetExpectedBlockSize(uint pId) { - return GetExpectedBlockSizeWithoutTransactions(Transactions!.Count) + // Base size - Transactions.Values.Sum(u => u.Size); // Sum Txs + return GetExpectedBlockSizeWithoutTransactions(Transactions[pId].Count) + // Base size + Transactions[pId].Values.Sum(u => u.Size); // Sum Txs } /// /// Return the expected block system fee /// - public long GetExpectedBlockSystemFee() + public long GetExpectedBlockSystemFee(uint pId) { - return Transactions!.Values.Sum(u => u.SystemFee); // Sum Txs + // check ! on the original todo + return Transactions[pId].Values.Sum(u => u.SystemFee); // Sum Txs } /// diff --git a/plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs b/plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs index 33bfe6f93..cf45edf3e 100644 --- a/plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs +++ b/plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs @@ -29,22 +29,22 @@ public ExtensiblePayload MakeChangeView(ChangeViewReason reason) }); } - public ExtensiblePayload MakeCommit() + public ExtensiblePayload MakeCommit(uint pId) { - if (CommitPayloads[MyIndex] is ExtensiblePayload payload) + if (CommitPayloads[pId][MyIndex] is ExtensiblePayload payload) return payload; - var block = EnsureHeader()!; - CommitPayloads[MyIndex] = MakeSignedPayload(new Commit + var block = EnsureHeader(pId)!; + CommitPayloads[pId][MyIndex] = MakeSignedPayload(new Commit { Signature = _signer.SignBlock(block, _myPublicKey!, dbftSettings.Network) }); - return CommitPayloads[MyIndex]!; + return CommitPayloads[pId][MyIndex]!; } private ExtensiblePayload MakeSignedPayload(ConsensusMessage message) { - message.BlockIndex = Block.Index; + message.BlockIndex = Block[0].Index; message.ValidatorIndex = (byte)MyIndex; message.ViewNumber = ViewNumber; ExtensiblePayload payload = CreatePayload(message, null); @@ -69,11 +69,11 @@ private void SignPayload(ExtensiblePayload payload) /// Prevent that block exceed the max size /// /// Ordered transactions - internal void EnsureMaxBlockLimitation(Transaction[] txs) + internal void EnsureMaxBlockLimitation(Transaction[] txs, uint pId) { var hashes = new List(); - Transactions = new Dictionary(); - VerificationContext = new TransactionVerificationContext(); + Transactions[pId] = new Dictionary(); + VerificationContext[pId] = new TransactionVerificationContext(); // Expected block size var blockSize = GetExpectedBlockSizeWithoutTransactions(txs.Length); @@ -91,27 +91,41 @@ internal void EnsureMaxBlockLimitation(Transaction[] txs) if (blockSystemFee > dbftSettings.MaxBlockSystemFee) break; hashes.Add(tx.Hash); - Transactions.Add(tx.Hash, tx); - VerificationContext.AddTransaction(tx); + Transactions[pId].Add(tx.Hash, tx); + VerificationContext[pId].AddTransaction(tx); } - TransactionHashes = hashes.ToArray(); + TransactionHashes[pId] = hashes.ToArray(); } - public ExtensiblePayload MakePrepareRequest() + internal IEnumerable PickTransactions() { var maxTransactionsPerBlock = neoSystem.Settings.MaxTransactionsPerBlock; + var verifiedTxes = neoSystem.MemPool.GetSortedVerifiedTransactions((int)maxTransactionsPerBlock)); + if (ViewNumber > 0 && LastProposal.Length > 0) + { + var txes = verifiedTxes.Where(p => LastProposal.Contains(p.Hash)); + if (txes.Count() > LastProposal.Length / 2) + return txes; + } + return verifiedTxes; + + } + public ExtensiblePayload MakePrepareRequest(uint pId) + { + + EnsureMaxBlockLimitation(PickTransactions(), pId); // Limit Speaker proposal to the limit `MaxTransactionsPerBlock` or all available transactions of the mempool - EnsureMaxBlockLimitation(neoSystem.MemPool.GetSortedVerifiedTransactions((int)maxTransactionsPerBlock)); - Block.Header.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestampMS(), PrevHeader.Timestamp + 1); - Block.Header.Nonce = GetNonce(); - return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareRequest + + Block[pId].Header.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestampMS(), PrevHeader.Timestamp + 1); + Block[pId].Header.Nonce = GetNonce(); + return PreparationPayloads[pId][MyIndex] = MakeSignedPayload(new PrepareRequest { - Version = Block.Version, - PrevHash = Block.PrevHash, - Timestamp = Block.Timestamp, - Nonce = Block.Nonce, - TransactionHashes = TransactionHashes! + Version = Block[pId].Version, + PrevHash = Block[pId].PrevHash, + Timestamp = Block[pId].Timestamp, + Nonce = Block[pId].Nonce, + TransactionHashes = TransactionHashes![pId] }); } @@ -126,52 +140,64 @@ public ExtensiblePayload MakeRecoveryRequest() public ExtensiblePayload MakeRecoveryMessage() { PrepareRequest? prepareRequestMessage = null; - if (TransactionHashes != null) + uint pId = TransactionHashes[0] != null ? 0u : (TransactionHashes[1] != null ? 1u : 0u); + if (TransactionHashes[pId] != null) { prepareRequestMessage = new PrepareRequest { - Version = Block.Version, - PrevHash = Block.PrevHash, + Version = Block[pId].Version, + PrevHash = Block[pId].PrevHash, ViewNumber = ViewNumber, - Timestamp = Block.Timestamp, - Nonce = Block.Nonce, - BlockIndex = Block.Index, - ValidatorIndex = Block.PrimaryIndex, - TransactionHashes = TransactionHashes + Timestamp = Block[pId].Timestamp, + Nonce = Block[pId].Nonce, + BlockIndex = Block[pId].Index, + ValidatorIndex = Block[pId].PrimaryIndex, + TransactionHashes = TransactionHashes[pId] }; } return MakeSignedPayload(new RecoveryMessage { + PId = pId, ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null) .Select(p => GetChangeViewPayloadCompact(p!)) .Take(M) .ToDictionary(p => p.ValidatorIndex), PrepareRequestMessage = prepareRequestMessage, // We only need a PreparationHash set if we don't have the PrepareRequest information. - PreparationHash = TransactionHashes == null - ? PreparationPayloads.Where(p => p != null) + PreparationHash = TransactionHashes[pId] == null + ? PreparationPayloads[pId].Where(p => p != null) .GroupBy(p => GetMessage(p!).PreparationHash, (k, g) => new { Hash = k, Count = g.Count() }) .OrderByDescending(p => p.Count) .Select(p => p.Hash) .FirstOrDefault() : null, - PreparationMessages = PreparationPayloads.Where(p => p != null) + PreparationMessages = PreparationPayloads[pId].Where(p => p != null) .Select(p => GetPreparationPayloadCompact(p!)) .ToDictionary(p => p.ValidatorIndex), + PreCommitMessages = PreCommitPayloads[pId].Where(p => p != null).Select(p => GetPreCommitPayloadCompact(p)).ToDictionary(p => p.ValidatorIndex), CommitMessages = CommitSent - ? CommitPayloads.Where(p => p != null).Select(p => GetCommitPayloadCompact(p!)).ToDictionary(p => p.ValidatorIndex) + ? CommitPayloads[pId].Where(p => p != null).Select(p => GetCommitPayloadCompact(p!)).ToDictionary(p => p.ValidatorIndex) : new Dictionary() }); } - public ExtensiblePayload MakePrepareResponse() + public ExtensiblePayload MakePrepareResponse(uint pId) { - return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareResponse + return PreparationPayloads[pId][MyIndex] = MakeSignedPayload(new PrepareResponse { - PreparationHash = PreparationPayloads[Block.PrimaryIndex]!.Hash + PreparationHash = PreparationPayloads[Block[pId].PrimaryIndex]!.Hash, + PId = pId }); } + public ExtensiblePayload MakePreCommit(uint pId) + { + return PreCommitPayloads[pId][MyIndex] = MakeSignedPayload(new PreCommit + { + PreparationHash = PreparationPayloads[pId][Block[pId].PrimaryIndex].Hash, + PId = pId + }); + } // Related to issue https://github.com/neo-project/neo/issues/3431 // Ref. https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator?view=net-8.0 // diff --git a/plugins/DBFTPlugin/Consensus/ConsensusContext.cs b/plugins/DBFTPlugin/Consensus/ConsensusContext.cs index d807e19fa..9e0a14645 100644 --- a/plugins/DBFTPlugin/Consensus/ConsensusContext.cs +++ b/plugins/DBFTPlugin/Consensus/ConsensusContext.cs @@ -31,17 +31,27 @@ public sealed partial class ConsensusContext : IDisposable, ISerializable /// private static readonly byte[] ConsensusStateKey = { 0xf4 }; - public Block Block = null!; + //public Block Block = null!; Check if they should be set to null + public Block[] Block = new Block[2]; public byte ViewNumber; public TimeSpan TimePerBlock; public ECPoint[] Validators = null!; public int MyIndex; - public UInt256[]? TransactionHashes; - public Dictionary? Transactions; - public ExtensiblePayload?[] PreparationPayloads = null!; - public ExtensiblePayload?[] CommitPayloads = null!; + + //Check the ? on Transaction and null! for the others + //public Dictionary? Transactions; + //public ExtensiblePayload?[] PreparationPayloads = null!; + //public ExtensiblePayload?[] CommitPayloads = null!; + public UInt256[][] TransactionHashes = new UInt256[2][]; + public Dictionary[] Transactions = new Dictionary[2]; + public ExtensiblePayload[][] PreparationPayloads = new ExtensiblePayload[2][]; + public ExtensiblePayload[][] PreCommitPayloads = new ExtensiblePayload[2][]; + public ExtensiblePayload[][] CommitPayloads = new ExtensiblePayload[2][]; + public ExtensiblePayload?[] ChangeViewPayloads = null!; public ExtensiblePayload?[] LastChangeViewPayloads = null!; + public UInt256[] LastProposal; + // 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; private set; } @@ -49,7 +59,7 @@ public sealed partial class ConsensusContext : IDisposable, ISerializable /// /// Store all verified unsorted transactions' senders' fee currently in the consensus context. /// - public TransactionVerificationContext VerificationContext = new(); + public TransactionVerificationContext[] VerificationContext = new TransactionVerificationContext[2]; public StoreCache Snapshot { get; private set; } = null!; private ECPoint? _myPublicKey; @@ -62,17 +72,24 @@ public sealed partial class ConsensusContext : IDisposable, ISerializable public int F => (Validators.Length - 1) / 3; public int M => Validators.Length - F; - public bool IsPrimary => MyIndex == Block.PrimaryIndex; - public bool IsBackup => MyIndex >= 0 && MyIndex != Block.PrimaryIndex; + public bool IsPriorityPrimary => MyIndex == Block[0].PrimaryIndex; + public bool IsFallbackPrimary => ViewNumber == 0 && MyIndex == Block[1].PrimaryIndex; + public bool IsAPrimary => IsPriorityPrimary || IsFallbackPrimary; + //Modify to be 1 or 4/3 + public static float PrimaryTimerPriorityMultiplier => 1; + public static float PrimaryTimerFallBackMultiplier => (float)4 / 3; + public float PrimaryTimerMultiplier => IsPriorityPrimary ? PrimaryTimerPriorityMultiplier : PrimaryTimerFallBackMultiplier; + public bool IsBackup => MyIndex >= 0 && !IsPriorityPrimary && !IsFallbackPrimary; + public bool WatchOnly => MyIndex < 0; - public Header PrevHeader => NativeContract.Ledger.GetHeader(Snapshot, Block.PrevHash)!; - public int CountCommitted => CommitPayloads.Count(p => p != null); + public Header PrevHeader => NativeContract.Ledger.GetHeader(Snapshot, Block[0].PrevHash)!; + public int CountCommitted => CommitPayloads[0].Count(p => p != null) + CommitPayloads[1].Count(p => p != null); public int CountFailed { get { if (LastSeenMessage == null) return 0; - return Validators.Count(p => !LastSeenMessage.TryGetValue(p, out var value) || value < (Block.Index - 1)); + return Validators.Count(p => !LastSeenMessage.TryGetValue(p, out var value) || value < (Block[0].Index - 1)); } } public bool ValidatorsChanged @@ -88,10 +105,13 @@ public bool ValidatorsChanged } #region Consensus States - public bool RequestSentOrReceived => PreparationPayloads[Block.PrimaryIndex] != null; - public bool ResponseSent => !WatchOnly && PreparationPayloads[MyIndex] != null; - public bool CommitSent => !WatchOnly && CommitPayloads[MyIndex] != null; - public bool BlockSent => Block.Transactions != null; + public bool RequestSentOrReceived => PreparationPayloads[0][Block[0].PrimaryIndex] != null || (ViewNumber == 0 && PreparationPayloads[1][Block[1].PrimaryIndex] != null); + public bool ResponseSent => !WatchOnly && (PreparationPayloads[0][MyIndex] != null || (ViewNumber == 0 && PreparationPayloads[1][MyIndex] != null)); + public bool PreCommitSent => !WatchOnly && (PreCommitPayloads[0][MyIndex] != null || (ViewNumber == 0 && PreCommitPayloads[1][MyIndex] != null)); + public bool CommitSent => !WatchOnly && (CommitPayloads[0][MyIndex] != null || (ViewNumber == 0 && CommitPayloads[1][MyIndex] != null)); + public bool BlockSent => Block[0].Transactions != null || Block[1]?.Transactions != null; + + public bool ViewChanging => !WatchOnly && GetMessage(ChangeViewPayloads[MyIndex])?.NewViewNumber > ViewNumber; // NotAcceptingPayloadsDueToViewChanging imposes nodes to not accept some payloads if View is Changing, // i.e: OnTransaction function will not process any transaction; OnPrepareRequestReceived will also return; @@ -119,20 +139,20 @@ public ConsensusContext(NeoSystem neoSystem, DbftSettings settings, ISigner sign store = neoSystem.LoadStore(settings.RecoveryLogs); } - public Block CreateBlock() + public Block CreateBlock(uint pID) { - EnsureHeader(); + EnsureHeader(pID); var contract = Contract.CreateMultiSigContract(M, Validators); - var sc = new ContractParametersContext(neoSystem.StoreView, Block.Header, dbftSettings.Network); + var sc = new ContractParametersContext(neoSystem.StoreView, Block[pID].Header, dbftSettings.Network); for (int i = 0, j = 0; i < Validators.Length && j < M; i++) { - if (GetMessage(CommitPayloads[i])?.ViewNumber != ViewNumber) continue; - sc.AddSignature(contract, Validators[i], GetMessage(CommitPayloads[i]!).Signature.ToArray()); + if (GetMessage(CommitPayloads[pID][i])?.ViewNumber != ViewNumber) continue; + sc.AddSignature(contract, Validators[i], GetMessage(CommitPayloads[pID][i]!).Signature.ToArray()); j++; } - Block.Header.Witness = sc.GetWitnesses()[0]; - Block.Transactions = TransactionHashes!.Select(p => Transactions![p]).ToArray(); - return Block; + Block[pID].Header.Witness = sc.GetWitnesses()[0]; + Block[pID].Transactions = TransactionHashes!.Select(p => Transactions![p]).ToArray(); + return Block[pID]; } public ExtensiblePayload CreatePayload(ConsensusMessage message, ReadOnlyMemory invocationScript = default) @@ -159,12 +179,11 @@ public void Dispose() Snapshot?.Dispose(); } - public Block? EnsureHeader() + public Block? EnsureHeader(uint pID) { if (TransactionHashes == null) return null; - Block.Header.MerkleRoot ??= MerkleTree.ComputeRoot(TransactionHashes); - return Block; - } + Block[pID].Header.MerkleRoot ??= MerkleTree.ComputeRoot(TransactionHashes[pID]); + return Block[pID]; public bool Load() { @@ -195,21 +214,24 @@ public void Reset(byte viewNumber) Snapshot?.Dispose(); Snapshot = neoSystem.GetSnapshotCache(); uint height = NativeContract.Ledger.CurrentIndex(Snapshot); - Block = new Block + for (uint i = 0; i <= 1; i++) { - Header = new Header + Block[i] = new Block { - PrevHash = NativeContract.Ledger.CurrentHash(Snapshot), - MerkleRoot = null!, - Index = height + 1, - NextConsensus = Contract.GetBFTAddress( - NeoToken.ShouldRefreshCommittee(height + 1, neoSystem.Settings.CommitteeMembersCount) ? - NativeContract.NEO.ComputeNextBlockValidators(Snapshot, neoSystem.Settings) : - NativeContract.NEO.GetNextBlockValidators(Snapshot, neoSystem.Settings.ValidatorsCount)), - Witness = null! - }, - Transactions = null! - }; + Header = new Header + { + PrevHash = NativeContract.Ledger.CurrentHash(Snapshot), + MerkleRoot = null!, + Index = height + 1, + NextConsensus = Contract.GetBFTAddress( + NeoToken.ShouldRefreshCommittee(height + 1, neoSystem.Settings.CommitteeMembersCount) ? + NativeContract.NEO.ComputeNextBlockValidators(Snapshot, neoSystem.Settings) : + NativeContract.NEO.GetNextBlockValidators(Snapshot, neoSystem.Settings.ValidatorsCount)), + Witness = null! + }, + Transactions = null! + }; + } TimePerBlock = neoSystem.Settings.TimePerBlock; var pv = Validators; Validators = NativeContract.NEO.GetNextBlockValidators(Snapshot, neoSystem.Settings.ValidatorsCount); @@ -231,7 +253,7 @@ public void Reset(byte viewNumber) MyIndex = -1; ChangeViewPayloads = new ExtensiblePayload[Validators.Length]; LastChangeViewPayloads = new ExtensiblePayload[Validators.Length]; - CommitPayloads = new ExtensiblePayload[Validators.Length]; + if (ValidatorsChanged || LastSeenMessage is null) { var previous_last_seen_message = LastSeenMessage; @@ -255,6 +277,21 @@ public void Reset(byte viewNumber) break; } cachedMessages = new Dictionary(); + LastProposal = Array.Empty(); + for (uint pID = 0; pID <= 1; pID++) + { + Block[pID].Header.MerkleRoot = null; + Block[pID].Header.Timestamp = 0; + Block[pID].Header.Nonce = 0; + Block[pID].Transactions = null; + TransactionHashes[pID] = null; + PreparationPayloads[pID] = new ExtensiblePayload[Validators.Length]; + PreCommitPayloads[pID] = new ExtensiblePayload[Validators.Length]; + CommitPayloads[pID] = new ExtensiblePayload[Validators.Length]; + if (MyIndex >= 0) LastSeenMessage[Validators[MyIndex]] = Block[pID].Index; + } + Block[0].Header.PrimaryIndex = GetPriorityPrimaryIndex(viewNumber); + Block[1].Header.PrimaryIndex = GetFallbackPrimaryIndex(Block[0].Header.PrimaryIndex); } else { @@ -263,16 +300,25 @@ public void Reset(byte viewNumber) LastChangeViewPayloads[i] = ChangeViewPayloads[i]; else LastChangeViewPayloads[i] = null; + + Block[0].Header.MerkleRoot = null; + Block[0].Header.Timestamp = 0; + Block[0].Header.Nonce = 0; + Block[0].Transactions = null; + TransactionHashes[0] = null; + PreparationPayloads[0] = new ExtensiblePayload[Validators.Length]; + PreCommitPayloads[0] = new ExtensiblePayload[Validators.Length]; + if (MyIndex >= 0) LastSeenMessage[Validators[MyIndex]] = Block[0].Index; + Block[0].Header.PrimaryIndex = GetPriorityPrimaryIndex(viewNumber); + + Block[1] = null; + TransactionHashes[1] = null; + Transactions[1] = null; + VerificationContext[1] = null; + PreparationPayloads[1] = null; + PreCommitPayloads[1] = null; } ViewNumber = viewNumber; - Block.Header.PrimaryIndex = GetPrimaryIndex(viewNumber); - Block.Header.MerkleRoot = null!; - Block.Header.Timestamp = 0; - Block.Header.Nonce = 0; - Block.Transactions = null!; - TransactionHashes = null; - PreparationPayloads = new ExtensiblePayload[Validators.Length]; - if (MyIndex >= 0) LastSeenMessage![Validators[MyIndex]] = Block.Index; } public void Save() @@ -283,50 +329,71 @@ public void Save() public void Deserialize(ref MemoryReader reader) { Reset(0); - - var blockVersion = reader.ReadUInt32(); - if (blockVersion != Block.Version) - throw new FormatException($"Invalid block version: {blockVersion}/{Block.Version}"); - - if (reader.ReadUInt32() != Block.Index) throw new InvalidOperationException(); - Block.Header.Timestamp = reader.ReadUInt64(); - Block.Header.Nonce = reader.ReadUInt64(); - Block.Header.PrimaryIndex = reader.ReadByte(); - Block.Header.NextConsensus = reader.ReadSerializable(); - if (Block.NextConsensus.Equals(UInt160.Zero)) - Block.Header.NextConsensus = null!; ViewNumber = reader.ReadByte(); - TransactionHashes = reader.ReadSerializableArray(ushort.MaxValue); - Transaction[] transactions = reader.ReadSerializableArray(ushort.MaxValue); - PreparationPayloads = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); - CommitPayloads = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); - ChangeViewPayloads = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); - LastChangeViewPayloads = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); - if (TransactionHashes.Length == 0 && !RequestSentOrReceived) - TransactionHashes = null; - Transactions = transactions.Length == 0 && !RequestSentOrReceived ? null : transactions.ToDictionary(p => p.Hash); - VerificationContext = new TransactionVerificationContext(); - if (Transactions != null) + + for (uint pID = 0; pID <= 1; pID++) { - foreach (Transaction tx in Transactions.Values) - VerificationContext.AddTransaction(tx); + if (ViewNumber > 0 && pID > 0) break; + + var blockVersion = reader.ReadUInt32(); + if (blockVersion != Block[pID].Version) + throw new FormatException($"Invalid block version: {blockVersion}/{Block[pID].Version}"); + if (reader.ReadUInt32() != Block[pID].Index) throw new InvalidOperationException(); + + Block[pID].Header.Timestamp = reader.ReadUInt64(); + Block[pID].Header.Nonce = reader.ReadUInt64(); + Block[pID].Header.PrimaryIndex = reader.ReadByte(); + Block[pID].Header.NextConsensus = reader.ReadSerializable(); + if (Block[pID].NextConsensus.Equals(UInt160.Zero)) + Block[pID].Header.NextConsensus = null!; + + TransactionHashes[pID] = reader.ReadSerializableArray(ushort.MaxValue); + Transaction[] transactions = reader.ReadSerializableArray(ushort.MaxValue); + PreparationPayloads[pID] = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); + PreCommitPayloads[pID] = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); + CommitPayloads[pID] = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); + + if (TransactionHashes[pID].Length == 0 && !RequestSentOrReceived) + TransactionHashes[pID] = null; + Transactions[pID] = transactions.Length == 0 && !RequestSentOrReceived ? null : transactions.ToDictionary(p => p.Hash); + VerificationContext[pID] = new TransactionVerificationContext(); + if (Transactions[pID] != null) + { + foreach (Transaction tx in Transactions[pID].Values) + VerificationContext[pID].AddTransaction(tx); + } } + ChangeViewPayloads[pID] = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); + LastChangeViewPayloads[pID] = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); } public void Serialize(BinaryWriter writer) { - writer.Write(Block.Version); - writer.Write(Block.Index); - writer.Write(Block.Timestamp); - writer.Write(Block.Nonce); - writer.Write(Block.PrimaryIndex); - writer.Write(Block.NextConsensus ?? UInt160.Zero); + writer.Write(ViewNumber); - writer.Write(TransactionHashes ?? Array.Empty()); - writer.Write(Transactions?.Values.ToArray() ?? Array.Empty()); - writer.WriteNullableArray(PreparationPayloads); - writer.WriteNullableArray(CommitPayloads); + for (uint i = 0; i <= 1; i++) + { + if (ViewNumber > 0 && i > 0) break; + writer.Write(Block[i].Version); + writer.Write(Block[i].Index); + writer.Write(Block[i].Timestamp); + writer.Write(Block[i].Nonce); + writer.Write(Block[i].PrimaryIndex); + writer.Write(Block[i].NextConsensus ?? UInt160.Zero); + writer.Write(TransactionHashes[i] ?? Array.Empty()); + writer.Write(Transactions[i]?.Values.ToArray() ?? Array.Empty()); + writer.WriteNullableArray(PreparationPayloads[i]); + writer.WriteNullableArray(PreCommitPayloads[i]); + writer.WriteNullableArray(CommitPayloads[i]); + } + writer.WriteNullableArray(ChangeViewPayloads); writer.WriteNullableArray(LastChangeViewPayloads); } + + + private static void Log(string message, LogLevel level = LogLevel.Info) + { + Utility.Log(nameof(ConsensusService), level, message); + } } diff --git a/plugins/DBFTPlugin/Consensus/ConsensusService.Check.cs b/plugins/DBFTPlugin/Consensus/ConsensusService.Check.cs index ee6e60f07..5c724e136 100644 --- a/plugins/DBFTPlugin/Consensus/ConsensusService.Check.cs +++ b/plugins/DBFTPlugin/Consensus/ConsensusService.Check.cs @@ -19,25 +19,25 @@ namespace Neo.Plugins.DBFTPlugin.Consensus; partial class ConsensusService { - private bool CheckPrepareResponse() + private bool CheckPrepareResponse(uint pId) { - if (context.TransactionHashes!.Length == context.Transactions!.Count) + if (context.TransactionHashes[pId]!.Length == context.Transactions[pId]!.Count) { // if we are the primary for this view, but acting as a backup because we recovered our own // previously sent prepare request, then we don't want to send a prepare response. - if (context.IsPrimary || context.WatchOnly) return true; + if ((pId == 0 && context.IsPriorityPrimary) || (pId == 1 && context.IsFallbackPrimary) || context.WatchOnly) return true; // Check maximum block size via Native Contract policy - if (context.GetExpectedBlockSize() > dbftSettings.MaxBlockSize) + if (context.GetExpectedBlockSize(pId) > dbftSettings.MaxBlockSize) { - Log($"Rejected block: {context.Block.Index} The size exceed the policy", LogLevel.Warning); + Log($"Rejected block: {context.Block[pId].Index} The size exceed the policy", LogLevel.Warning); RequestChangeView(ChangeViewReason.BlockRejectedByPolicy); return false; } // Check maximum block system fee via Native Contract policy - if (context.GetExpectedBlockSystemFee() > dbftSettings.MaxBlockSystemFee) + if (context.GetExpectedBlockSystemFee(pId) > dbftSettings.MaxBlockSystemFee) { - Log($"Rejected block: {context.Block.Index} The system fee exceed the policy", LogLevel.Warning); + Log($"Rejected block: {context.Block[pId].Index} The system fee exceed the policy", LogLevel.Warning); RequestChangeView(ChangeViewReason.BlockRejectedByPolicy); return false; } @@ -47,20 +47,35 @@ private bool CheckPrepareResponse() ExtendTimerByFactor(2); Log($"Sending {nameof(PrepareResponse)}"); - localNode.Tell(new LocalNode.SendDirectly(context.MakePrepareResponse())); - CheckPreparations(); + localNode.Tell(new LocalNode.SendDirectly(context.MakePrepareResponse(pId))); + CheckPreparations(pId); } return true; } - private void CheckCommits() + private void CheckPreCommits(uint pId, bool forced = false) { - if (context.CommitPayloads.Count(p => context.GetMessage(p)?.ViewNumber == context.ViewNumber) >= context.M && context.TransactionHashes!.All(p => context.Transactions!.ContainsKey(p))) + if (forced || context.PreCommitPayloads[pId].Count(p => p != null) >= context.M && context.TransactionHashes[pId].All(p => context.Transactions[pId].ContainsKey(p))) { - block_received_index = context.Block.Index; - Block block = context.CreateBlock(); - Log($"Sending {nameof(Block)}: height={block.Index} hash={block.Hash} tx={block.Transactions.Length}"); + ExtensiblePayload payload = context.MakeCommit(pId); + Log($"Sending {nameof(Commit)} to pId={pId}"); + context.Save(); + localNode.Tell(new LocalNode.SendDirectly { Inventory = payload }); + // Set timer, so we will resend the commit in case of a networking issue + ChangeTimer(TimeSpan.FromMilliseconds(neoSystem.Settings.MillisecondsPerBlock)); + CheckCommits(pId); + } + } + + private void CheckCommits(uint pId) + { + if (context.CommitPayloads[pId].Count(p => context.GetMessage(p)?.ViewNumber == context.ViewNumber) >= context.M && context.TransactionHashes[pId]!.All(p => context.Transactions[pId]!.ContainsKey(p))) + { + block_received_index = context.Block[pId].Index; + Block block = context.CreateBlock(pId); + Log($"Sending {nameof(Block)}: height={block.Index} hash={block.Hash} tx={block.Transactions.Length} Id={pId}"); blockchain.Tell(block); + return; } } @@ -83,17 +98,35 @@ private void CheckExpectedView(byte viewNumber) } } - private void CheckPreparations() + private void CheckPreparations(uint pId) { - if (context.PreparationPayloads.Count(p => p != null) >= context.M && context.TransactionHashes!.All(p => context.Transactions!.ContainsKey(p))) - { - ExtensiblePayload payload = context.MakeCommit(); - Log($"Sending {nameof(Commit)}"); - context.Save(); - localNode.Tell(new LocalNode.SendDirectly(payload)); - // Set timer, so we will resend the commit in case of a networking issue - ChangeTimer(context.TimePerBlock); - CheckCommits(); - } + if (context.PreparationPayloads.Count(p => p != null) >= context.M && context.TransactionHashes[pId]!.All(p => context.Transactions[pId]!.ContainsKey(p))) + if (context.TransactionHashes[pId].All(p => context.Transactions[pId].ContainsKey(p))) + { + var preparationsCount = context.PreparationPayloads[pId].Count(p => p != null); + if (context.ViewNumber > 0) + { + if (preparationsCount >= context.M) + CheckPreCommits(0, true); + return; + } + if (!context.PreCommitSent + && ((pId == 0 && preparationsCount >= context.F + 1) + || (pId == 1 && preparationsCount >= context.M))) + { + ExtensiblePayload payload = context.MakePreCommit(pId); + Log($"Sending {nameof(PreCommit)} to pId={pId}"); + context.Save(); + localNode.Tell(new LocalNode.SendDirectly(payload)); + // Set timer, so we will resend the commit in case of a networking issue + ChangeTimer(context.TimePerBlock); + CheckPreCommits(pId); + } + if (context.ViewNumber == 0 && pId == 0 && preparationsCount >= context.M) + { + CheckPreCommits(0, true); + } + + } } } diff --git a/plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs b/plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs index c7663d2dd..1ce7509b8 100644 --- a/plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs +++ b/plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs @@ -37,11 +37,11 @@ private void OnConsensusPayload(ExtensiblePayload payload) } if (!message.Verify(neoSystem.Settings)) return; - if (message.BlockIndex != context.Block.Index) + if (message.BlockIndex != context.Block[0].Index) { - if (context.Block.Index < message.BlockIndex) + if (context.Block[0].Index < message.BlockIndex) { - Log($"Chain is behind: expected={message.BlockIndex} current={context.Block.Index - 1}", LogLevel.Warning); + Log($"Chain is behind: expected={message.BlockIndex} current={context.Block[0].Index - 1}", LogLevel.Warning); } return; } @@ -59,6 +59,9 @@ private void OnConsensusPayload(ExtensiblePayload payload) case ChangeView view: OnChangeViewReceived(payload, view); break; + case PreCommit precommit: + OnPreCommitReceived(payload, precommit); + break; case Commit commit: OnCommitReceived(payload, commit); break; @@ -74,10 +77,11 @@ private void OnConsensusPayload(ExtensiblePayload payload) private void OnPrepareRequestReceived(ExtensiblePayload payload, PrepareRequest message) { if (context.RequestSentOrReceived || context.NotAcceptingPayloadsDueToViewChanging) return; - if (message.ValidatorIndex != context.Block.PrimaryIndex || message.ViewNumber != context.ViewNumber) return; - if (message.Version != context.Block.Version || message.PrevHash != context.Block.PrevHash) return; + uint pId = context.ViewNumber > 0 || message.ValidatorIndex == context.GetPriorityPrimaryIndex(context.ViewNumber) ? 0u : 1u; + if (message.ValidatorIndex != context.Block[pId].PrimaryIndex || message.ViewNumber != context.ViewNumber) return; + if (message.Version != context.Block[pId].Version || message.PrevHash != context.Block[pId].PrevHash) return; if (message.TransactionHashes.Length > neoSystem.Settings.MaxTransactionsPerBlock) return; - Log($"{nameof(OnPrepareRequestReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex} tx={message.TransactionHashes.Length}"); + Log($"{nameof(OnPrepareRequestReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex} tx={message.TransactionHashes.Length} priority={message.ValidatorIndex == context.Block[0].PrimaryIndex} fallback={context.ViewNumber == 0 && message.ValidatorIndex == context.Block[1].PrimaryIndex}"); if (message.Timestamp <= context.PrevHeader.Timestamp || message.Timestamp > TimeProvider.Current.UtcNow.AddMilliseconds(8 * context.TimePerBlock.TotalMilliseconds).ToTimestampMS()) { Log($"Timestamp incorrect: {message.Timestamp}", LogLevel.Warning); @@ -97,34 +101,35 @@ private void OnPrepareRequestReceived(ExtensiblePayload payload, PrepareRequest prepareRequestReceivedTime = TimeProvider.Current.UtcNow; prepareRequestReceivedBlockIndex = message.BlockIndex; - context.Block.Header.Timestamp = message.Timestamp; - context.Block.Header.Nonce = message.Nonce; - context.TransactionHashes = message.TransactionHashes; + context.Block[pId].Header.Timestamp = message.Timestamp; + context.Block[pId].Header.Nonce = message.Nonce; + context.TransactionHashes[pId] = message.TransactionHashes; + context.LastProposal = message.TransactionHashes; - context.Transactions = new Dictionary(); - context.VerificationContext = new TransactionVerificationContext(); - for (int i = 0; i < context.PreparationPayloads.Length; i++) - if (context.PreparationPayloads[i] != null) - if (!context.GetMessage(context.PreparationPayloads[i]!).PreparationHash.Equals(payload.Hash)) - context.PreparationPayloads[i] = null; - context.PreparationPayloads[message.ValidatorIndex] = payload; - byte[] hashData = context.EnsureHeader()!.GetSignData(neoSystem.Settings.Network); - for (int i = 0; i < context.CommitPayloads.Length; i++) - if (context.GetMessage(context.CommitPayloads[i])?.ViewNumber == context.ViewNumber) - if (!Crypto.VerifySignature(hashData, context.GetMessage(context.CommitPayloads[i]!).Signature.Span, context.Validators[i])) - context.CommitPayloads[i] = null; + context.Transactions[pId] = new Dictionary(); + context.VerificationContext[pId] = new TransactionVerificationContext(); + for (int i = 0; i < context.PreparationPayloads[pId].Length; i++) + if (context.PreparationPayloads[pId][i] != null) + if (!context.GetMessage(context.PreparationPayloads[pId][i]!).PreparationHash.Equals(payload.Hash)) + context.PreparationPayloads[pId][i] = null; + context.PreparationPayloads[pId][message.ValidatorIndex] = payload; + byte[] hashData = context.EnsureHeader(pId)!.GetSignData(neoSystem.Settings.Network); + for (int i = 0; i < context.CommitPayloads[pId].Length; i++) + if (context.GetMessage(context.CommitPayloads[pId][i])?.ViewNumber == context.ViewNumber) + if (!Crypto.VerifySignature(hashData, context.GetMessage(context.CommitPayloads[pId][i]!).Signature.Span, context.Validators[i])) + context.CommitPayloads[pId][i] = null; - if (context.TransactionHashes.Length == 0) + if (context.TransactionHashes[pId].Length == 0) { // There are no tx so we should act like if all the transactions were filled - CheckPrepareResponse(); + CheckPrepareResponse(pId); return; } Dictionary mempoolVerified = neoSystem.MemPool.GetVerifiedTransactions().ToDictionary(p => p.Hash); var unverified = new List(); var mtb = neoSystem.Settings.MaxTraceableBlocks; - foreach (UInt256 hash in context.TransactionHashes) + foreach (UInt256 hash in context.TransactionHashes[pId]) { if (mempoolVerified.TryGetValue(hash, out Transaction? tx)) { @@ -153,9 +158,9 @@ private void OnPrepareRequestReceived(ExtensiblePayload payload, PrepareRequest foreach (Transaction tx in unverified) if (!AddTransaction(tx, true)) return; - if (context.Transactions.Count < context.TransactionHashes.Length) + if (context.Transactions[pId].Count < context.TransactionHashes[pId].Length) { - UInt256[] hashes = context.TransactionHashes.Where(i => !context.Transactions.ContainsKey(i)).ToArray(); + UInt256[] hashes = context.TransactionHashes[pId].Where(i => !context.Transactions[pId].ContainsKey(i)).ToArray(); taskManager.Tell(new TaskManager.RestartTasks(InvPayload.Create(InventoryType.TX, hashes))); } } @@ -163,19 +168,38 @@ private void OnPrepareRequestReceived(ExtensiblePayload payload, PrepareRequest private void OnPrepareResponseReceived(ExtensiblePayload payload, PrepareResponse message) { if (message.ViewNumber != context.ViewNumber) return; - if (context.PreparationPayloads[message.ValidatorIndex] != null || context.NotAcceptingPayloadsDueToViewChanging) return; - if (context.PreparationPayloads[context.Block.PrimaryIndex] != null && !message.PreparationHash.Equals(context.PreparationPayloads[context.Block.PrimaryIndex]!.Hash)) + if (context.PreparationPayloads[message.PId][message.ValidatorIndex] != null || context.NotAcceptingPayloadsDueToViewChanging) return; + if (context.PreparationPayloads[message.PId][context.Block.PrimaryIndex] != null && !message.PreparationHash.Equals(context.PreparationPayloads[message.PId][context.Block[message.PId].PrimaryIndex]!.Hash)) return; // Timeout extension: prepare response has been received with success // around 2*15/M=30.0/5 ~ 40% block time (for M=5) ExtendTimerByFactor(2); - Log($"{nameof(OnPrepareResponseReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex}"); - context.PreparationPayloads[message.ValidatorIndex] = payload; + Log($"{nameof(OnPrepareResponseReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex} pId={message.PId}"); + context.PreparationPayloads[message.PId][message.ValidatorIndex] = payload; + if (context.WatchOnly || context.CommitSent) return; + if (context.RequestSentOrReceived) + CheckPreparations(message.PId); + } + + private void OnPreCommitReceived(ExtensiblePayload payload, PreCommit message) + { + if (message.ViewNumber != context.ViewNumber) return; + if (context.PreCommitPayloads[message.PId][message.ValidatorIndex] != null || context.NotAcceptingPayloadsDueToViewChanging) return; + if (context.RequestSentOrReceived) + { + // Check if we have joined another consensus process + if (context.PreparationPayloads[message.PId][context.Block[message.PId].PrimaryIndex] == null) return; + if (!message.PreparationHash.Equals(context.PreparationPayloads[message.PId][context.Block[message.PId].PrimaryIndex].Hash)) + return; + } + + Log($"{nameof(OnPreCommitReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex} pId={message.PId}"); + context.PreCommitPayloads[message.PId][message.ValidatorIndex] = payload; if (context.WatchOnly || context.CommitSent) return; if (context.RequestSentOrReceived) - CheckPreparations(); + CheckPreCommits(message.PId); } private void OnChangeViewReceived(ExtensiblePayload payload, ChangeView message) @@ -196,11 +220,11 @@ private void OnChangeViewReceived(ExtensiblePayload payload, ChangeView message) private void OnCommitReceived(ExtensiblePayload payload, Commit commit) { - ref ExtensiblePayload? existingCommitPayload = ref context.CommitPayloads[commit.ValidatorIndex]; + ref ExtensiblePayload? existingCommitPayload = ref context.CommitPayloads[commit.PId][commit.ValidatorIndex]; if (existingCommitPayload != null) { if (existingCommitPayload.Hash != payload.Hash) - Log($"Rejected {nameof(Commit)}: height={commit.BlockIndex} index={commit.ValidatorIndex} view={commit.ViewNumber} existingView={context.GetMessage(existingCommitPayload).ViewNumber}", LogLevel.Warning); + Log($"Rejected {nameof(Commit)}: height={commit.BlockIndex} index={commit.ValidatorIndex} view={commit.ViewNumber} existingView={context.GetMessage(existingCommitPayload).ViewNumber} pId={commit.PId}", LogLevel.Warning); return; } @@ -212,7 +236,7 @@ private void OnCommitReceived(ExtensiblePayload payload, Commit commit) Log($"{nameof(OnCommitReceived)}: height={commit.BlockIndex} view={commit.ViewNumber} index={commit.ValidatorIndex} nc={context.CountCommitted} nf={context.CountFailed}"); - byte[]? hashData = context.EnsureHeader()?.GetSignData(neoSystem.Settings.Network); + byte[]? hashData = context.EnsureHeader(commit.PId)?.GetSignData(neoSystem.Settings.Network); if (hashData == null) { existingCommitPayload = payload; @@ -220,7 +244,7 @@ private void OnCommitReceived(ExtensiblePayload payload, Commit commit) else if (Crypto.VerifySignature(hashData, commit.Signature.Span, context.Validators[commit.ValidatorIndex])) { existingCommitPayload = payload; - CheckCommits(); + CheckCommits(commit.PId); } return; } @@ -237,8 +261,9 @@ private void OnRecoveryMessageReceived(RecoveryMessage message) isRecovering = true; int validChangeViews = 0, totalChangeViews = 0, validPrepReq = 0, totalPrepReq = 0; int validPrepResponses = 0, totalPrepResponses = 0, validCommits = 0, totalCommits = 0; + int validPreCommits = 0, totalPreCommits = 0; - Log($"{nameof(OnRecoveryMessageReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex}"); + Log($"{nameof(OnRecoveryMessageReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex} pId={message.PId}"); try { if (message.ViewNumber > context.ViewNumber) @@ -249,6 +274,11 @@ private void OnRecoveryMessageReceived(RecoveryMessage message) foreach (ExtensiblePayload changeViewPayload in changeViewPayloads) if (ReverifyAndProcessPayload(changeViewPayload)) validChangeViews++; } + else if (context.IsAPrimary) + { + uint pId = Convert.ToUInt32(!context.IsPriorityPrimary); + SendPrepareRequest(pId); + } if (message.ViewNumber == context.ViewNumber && !context.NotAcceptingPayloadsDueToViewChanging && !context.CommitSent) { if (!context.RequestSentOrReceived) @@ -264,6 +294,10 @@ private void OnRecoveryMessageReceived(RecoveryMessage message) totalPrepResponses = prepareResponsePayloads.Length; foreach (ExtensiblePayload prepareResponsePayload in prepareResponsePayloads) if (ReverifyAndProcessPayload(prepareResponsePayload)) validPrepResponses++; + ExtensiblePayload[] preCommitPayloads = message.GetPreCommitPayloads(context); + totalPreCommits = preCommitPayloads.Length; + foreach (ExtensiblePayload preCommitPayload in preCommitPayloads) + if (ReverifyAndProcessPayload(preCommitPayload)) validPreCommits++; } if (message.ViewNumber <= context.ViewNumber) { @@ -276,7 +310,7 @@ private void OnRecoveryMessageReceived(RecoveryMessage message) } finally { - Log($"Recovery finished: (valid/total) ChgView: {validChangeViews}/{totalChangeViews} PrepReq: {validPrepReq}/{totalPrepReq} PrepResp: {validPrepResponses}/{totalPrepResponses} Commits: {validCommits}/{totalCommits}"); + Log($"Recovery finished: (valid/total) ChgView: {validChangeViews}/{totalChangeViews} PrepReq: {validPrepReq}/{totalPrepReq} PrepResp: {validPrepResponses}/{totalPrepResponses} PreCommits: {validPreCommits}/{totalPreCommits} Commits: {validCommits}/{totalCommits}"); isRecovering = false; } } diff --git a/plugins/DBFTPlugin/Messages/Commit.cs b/plugins/DBFTPlugin/Messages/Commit.cs index 75a75b120..f135322f8 100644 --- a/plugins/DBFTPlugin/Messages/Commit.cs +++ b/plugins/DBFTPlugin/Messages/Commit.cs @@ -18,7 +18,10 @@ public class Commit : ConsensusMessage { public ReadOnlyMemory Signature; - public override int Size => base.Size + Signature.Length; + // priority or fallback + public uint PId; + + public override int Size => base.Size + Signature.Length + sizeof(uint); public Commit() : base(ConsensusMessageType.Commit) { } @@ -26,11 +29,13 @@ public override void Deserialize(ref MemoryReader reader) { base.Deserialize(ref reader); Signature = reader.ReadMemory(64); + PId = reader.ReadUInt32(); } public override void Serialize(BinaryWriter writer) { base.Serialize(writer); writer.Write(Signature.Span); + writer.Write(PId); } } diff --git a/plugins/DBFTPlugin/Messages/PreCommit.cs b/plugins/DBFTPlugin/Messages/PreCommit.cs new file mode 100644 index 000000000..faca6d06e --- /dev/null +++ b/plugins/DBFTPlugin/Messages/PreCommit.cs @@ -0,0 +1,41 @@ +// Copyright (C) 2015-2026 The Neo Project. +// +// PreCommit.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Plugins.DBFTPlugin.Types; +using System.IO; + +namespace Neo.Plugins.DBFTPlugin.Messages; + +public class PreCommit : ConsensusMessage +{ + public UInt256 PreparationHash; + + // priority or fallback + public uint PId; + public override int Size => base.Size + PreparationHash.Size + sizeof(uint); + + public PreCommit() : base(ConsensusMessageType.PreCommit) { } + + public override void Deserialize(ref MemoryReader reader) + { + base.Deserialize(ref reader); + PreparationHash = reader.ReadSerializable(); + PId = reader.ReadUInt32(); + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(PreparationHash); + writer.Write(PId); + } +} diff --git a/plugins/DBFTPlugin/Messages/PrepareResponse.cs b/plugins/DBFTPlugin/Messages/PrepareResponse.cs index 55b0f3cfc..36dfbb028 100644 --- a/plugins/DBFTPlugin/Messages/PrepareResponse.cs +++ b/plugins/DBFTPlugin/Messages/PrepareResponse.cs @@ -19,7 +19,9 @@ public class PrepareResponse : ConsensusMessage { public required UInt256 PreparationHash; - public override int Size => base.Size + PreparationHash.Size; + // priority or fallback + public uint PId; + public override int Size => base.Size + PreparationHash.Size + sizeof(uint); public PrepareResponse() : base(ConsensusMessageType.PrepareResponse) { } @@ -27,11 +29,13 @@ public override void Deserialize(ref MemoryReader reader) { base.Deserialize(ref reader); PreparationHash = reader.ReadSerializable(); + PId = reader.ReadUInt32(); } public override void Serialize(BinaryWriter writer) { base.Serialize(writer); writer.Write(PreparationHash); + writer.Write(PId); } } diff --git a/plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreCommitPayloadCompact.cs b/plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreCommitPayloadCompact.cs new file mode 100644 index 000000000..825737620 --- /dev/null +++ b/plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreCommitPayloadCompact.cs @@ -0,0 +1,49 @@ +// Copyright (C) 2015-2026 The Neo Project. +// +// RecoveryMessage.PreCommitPayloadCompact.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.IO; + +namespace Neo.Plugins.DBFTPlugin.Messages; + +partial class RecoveryMessage +{ + public class PreCommitPayloadCompact : ISerializable + { + public byte ViewNumber; + public byte ValidatorIndex; + public UInt256 PreparationHash; + public ReadOnlyMemory InvocationScript; + + int ISerializable.Size => + sizeof(byte) + //ViewNumber + sizeof(byte) + //ValidatorIndex + UInt256.Length + //PreparationHash + InvocationScript.GetVarSize(); //InvocationScript + + void ISerializable.Deserialize(ref MemoryReader reader) + { + ViewNumber = reader.ReadByte(); + ValidatorIndex = reader.ReadByte(); + PreparationHash = reader.ReadSerializable(); + InvocationScript = reader.ReadVarMemory(1024); + } + + void ISerializable.Serialize(BinaryWriter writer) + { + writer.Write(ViewNumber); + writer.Write(ValidatorIndex); + writer.Write(PreparationHash); + writer.WriteVarBytes(InvocationScript.Span); + } + } +} diff --git a/plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs b/plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs index bc1bb1df9..23fc694dd 100644 --- a/plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs +++ b/plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs @@ -21,12 +21,14 @@ namespace Neo.Plugins.DBFTPlugin.Messages; public partial class RecoveryMessage : ConsensusMessage { + public uint PId; public required Dictionary ChangeViewMessages; public PrepareRequest? PrepareRequestMessage; /// The PreparationHash in case the PrepareRequest hasn't been received yet. /// This can be null if the PrepareRequest information is present, since it can be derived in that case. public UInt256? PreparationHash; public required Dictionary PreparationMessages; + public required Dictionary PreCommitMessages; public required Dictionary CommitMessages; public override int Size => base.Size @@ -41,6 +43,7 @@ public RecoveryMessage() : base(ConsensusMessageType.RecoveryMessage) { } public override void Deserialize(ref MemoryReader reader) { base.Deserialize(ref reader); + PId = reader.ReadUInt32(); ChangeViewMessages = reader.ReadSerializableArray(byte.MaxValue).ToDictionary(p => p.ValidatorIndex); if (reader.ReadBoolean()) { @@ -54,6 +57,7 @@ public override void Deserialize(ref MemoryReader reader) } PreparationMessages = reader.ReadSerializableArray(byte.MaxValue).ToDictionary(p => p.ValidatorIndex); + PreCommitMessages = reader.ReadSerializableArray(byte.MaxValue).ToDictionary(p => p.ValidatorIndex); CommitMessages = reader.ReadSerializableArray(byte.MaxValue).ToDictionary(p => p.ValidatorIndex); } @@ -73,6 +77,7 @@ internal ExtensiblePayload[] GetChangeViewPayloads(ConsensusContext context) BlockIndex = BlockIndex, ValidatorIndex = p.ValidatorIndex, ViewNumber = p.OriginalViewNumber, + PId = PId, Timestamp = p.Timestamp }, p.InvocationScript)).ToArray(); } @@ -91,27 +96,42 @@ internal ExtensiblePayload[] GetCommitPayloadsFromRecoveryMessage(ConsensusConte internal ExtensiblePayload? GetPrepareRequestPayload(ConsensusContext context) { if (PrepareRequestMessage == null) return null; - if (!PreparationMessages.TryGetValue(context.Block.PrimaryIndex, out PreparationPayloadCompact? compact)) + if (!PreparationMessages.TryGetValue(context.Block[PId].PrimaryIndex, out PreparationPayloadCompact? compact)) return null; return context.CreatePayload(PrepareRequestMessage, compact.InvocationScript); } internal ExtensiblePayload[] GetPrepareResponsePayloads(ConsensusContext context) { - UInt256? preparationHash = PreparationHash ?? context.PreparationPayloads[context.Block.PrimaryIndex]?.Hash; + UInt256? preparationHash = PreparationHash ?? context.PreparationPayloads[context.Block[PId].PrimaryIndex]?.Hash; if (preparationHash is null) return Array.Empty(); - return PreparationMessages.Values.Where(p => p.ValidatorIndex != context.Block.PrimaryIndex).Select(p => context.CreatePayload(new PrepareResponse + return PreparationMessages.Values.Where(p => p.ValidatorIndex != context.Block[0].PrimaryIndex).Select(p => context.CreatePayload(new PrepareResponse { BlockIndex = BlockIndex, ValidatorIndex = p.ValidatorIndex, ViewNumber = ViewNumber, + PId = PId, PreparationHash = preparationHash }, p.InvocationScript)).ToArray(); } + internal ExtensiblePayload[] GetPreCommitPayloads(ConsensusContext context) + { + return PreCommitMessages.Values.Select(p => context.CreatePayload(new PreCommit + { + BlockIndex = BlockIndex, + ViewNumber = p.ViewNumber, + ValidatorIndex = p.ValidatorIndex, + PreparationHash = p.PreparationHash, + PId = PId, + }, p.InvocationScript)).ToArray(); + } + + public override void Serialize(BinaryWriter writer) { base.Serialize(writer); + writer.Write(PId); writer.Write(ChangeViewMessages.Values.ToArray()); if (PrepareRequestMessage != null) { @@ -128,6 +148,7 @@ public override void Serialize(BinaryWriter writer) } writer.Write(PreparationMessages.Values.ToArray()); + writer.Write(PreCommitMessages.Values.ToArray()); writer.Write(CommitMessages.Values.ToArray()); } } diff --git a/plugins/DBFTPlugin/Types/ConsensusMessageType.cs b/plugins/DBFTPlugin/Types/ConsensusMessageType.cs index 7132db1b8..6f22bc671 100644 --- a/plugins/DBFTPlugin/Types/ConsensusMessageType.cs +++ b/plugins/DBFTPlugin/Types/ConsensusMessageType.cs @@ -18,6 +18,7 @@ public enum ConsensusMessageType : byte PrepareRequest = 0x20, PrepareResponse = 0x21, Commit = 0x30, + PreCommit = 0x31, RecoveryRequest = 0x40, RecoveryMessage = 0x41,