diff --git a/neo/Consensus/ConsensusContext.cs b/neo/Consensus/ConsensusContext.cs index d527e3d029..058ade225a 100644 --- a/neo/Consensus/ConsensusContext.cs +++ b/neo/Consensus/ConsensusContext.cs @@ -1,402 +1,396 @@ -using Neo.Cryptography; -using Neo.Cryptography.ECC; -using Neo.IO; -using Neo.Ledger; -using Neo.Network.P2P.Payloads; -using Neo.Persistence; -using Neo.Plugins; -using Neo.SmartContract; -using Neo.SmartContract.Native; -using Neo.Wallets; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace Neo.Consensus -{ - internal class ConsensusContext : IDisposable, ISerializable - { - /// - /// Prefix for saving consensus state. - /// - public const byte CN_Context = 0xf4; - - public Block Block; - public byte ViewNumber; - public ECPoint[] Validators; - public int MyIndex; - public UInt256[] TransactionHashes; - public Dictionary Transactions; - public ConsensusPayload[] PreparationPayloads; - public ConsensusPayload[] CommitPayloads; - public ConsensusPayload[] ChangeViewPayloads; - 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 Snapshot Snapshot { get; private set; } - private KeyPair keyPair; - private readonly Wallet wallet; - private readonly Store store; - private readonly Random random = new Random(); - - public int F => (Validators.Length - 1) / 3; - public int M => Validators.Length - F; - public bool IsPrimary => MyIndex == Block.ConsensusData.PrimaryIndex; - public bool IsBackup => MyIndex >= 0 && MyIndex != Block.ConsensusData.PrimaryIndex; - 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)); - - #region Consensus States - public bool RequestSentOrReceived => PreparationPayloads[Block.ConsensusData.PrimaryIndex] != null; - public bool ResponseSent => !WatchOnly && PreparationPayloads[MyIndex] != null; - public bool CommitSent => !WatchOnly && CommitPayloads[MyIndex] != null; - public bool BlockSent => Block.Transactions != null; - public bool ViewChanging => !WatchOnly && ChangeViewPayloads[MyIndex]?.GetDeserializedMessage().NewViewNumber > ViewNumber; - public bool NotAcceptingPayloadsDueToViewChanging => ViewChanging && !MoreThanFNodesCommittedOrLost; - // A possible attack can happen if the last node to commit is malicious and either sends change view after his - // commit to stall nodes in a higher view, or if he refuses to send recovery messages. In addition, if a node - // asking change views loses network or crashes and comes back when nodes are committed in more than one higher - // numbered view, it is possible for the node accepting recovery to commit in any of the higher views, thus - // potentially splitting nodes among views and stalling the network. - public bool MoreThanFNodesCommittedOrLost => (CountCommitted + CountFailed) > F; - #endregion - - public int Size => throw new NotImplementedException(); - - public ConsensusContext(Wallet wallet, Store store) - { - this.wallet = wallet; - this.store = store; - } - - public Block CreateBlock() - { - Contract contract = Contract.CreateMultiSigContract(M, Validators); - ContractParametersContext sc = new ContractParametersContext(Block); - for (int i = 0, j = 0; i < Validators.Length && j < M; i++) - { - if (CommitPayloads[i]?.ConsensusMessage.ViewNumber != ViewNumber) continue; - sc.AddSignature(contract, Validators[i], CommitPayloads[i].GetDeserializedMessage().Signature); - j++; - } - Block.Witness = sc.GetWitnesses()[0]; - Block.Transactions = TransactionHashes.Select(p => Transactions[p]).ToArray(); - return Block; - } - - public void Deserialize(BinaryReader reader) - { - Reset(0); - if (reader.ReadUInt32() != Block.Version) throw new FormatException(); - if (reader.ReadUInt32() != Block.Index) throw new InvalidOperationException(); - Block.Timestamp = reader.ReadUInt64(); - Block.NextConsensus = reader.ReadSerializable(); - if (Block.NextConsensus.Equals(UInt160.Zero)) - Block.NextConsensus = null; - Block.ConsensusData = reader.ReadSerializable(); - ViewNumber = reader.ReadByte(); - TransactionHashes = reader.ReadSerializableArray(); - if (TransactionHashes.Length == 0) - TransactionHashes = null; - Transaction[] transactions = reader.ReadSerializableArray(Block.MaxTransactionsPerBlock); - Transactions = transactions.Length == 0 ? null : transactions.ToDictionary(p => p.Hash); - PreparationPayloads = new ConsensusPayload[reader.ReadVarInt(Blockchain.MaxValidators)]; - for (int i = 0; i < PreparationPayloads.Length; i++) - PreparationPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; - CommitPayloads = new ConsensusPayload[reader.ReadVarInt(Blockchain.MaxValidators)]; - for (int i = 0; i < CommitPayloads.Length; i++) - CommitPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; - ChangeViewPayloads = new ConsensusPayload[reader.ReadVarInt(Blockchain.MaxValidators)]; - for (int i = 0; i < ChangeViewPayloads.Length; i++) - ChangeViewPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; - LastChangeViewPayloads = new ConsensusPayload[reader.ReadVarInt(Blockchain.MaxValidators)]; - for (int i = 0; i < LastChangeViewPayloads.Length; i++) - LastChangeViewPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; - } - - public void Dispose() - { - Snapshot?.Dispose(); - } - - public Block EnsureHeader() - { - if (TransactionHashes == null) return null; - if (Block.MerkleRoot is null) - Block.MerkleRoot = Block.CalculateMerkleRoot(Block.ConsensusData.Hash, TransactionHashes); - return Block; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetPrimaryIndex(byte viewNumber) - { - int p = ((int)Block.Index - viewNumber) % Validators.Length; - return p >= 0 ? (uint)p : (uint)(p + Validators.Length); - } - - public bool Load() - { - byte[] data = store.Get(CN_Context, new byte[0]); - if (data is null || data.Length == 0) return false; - using (MemoryStream ms = new MemoryStream(data, false)) - using (BinaryReader reader = new BinaryReader(ms)) - { - try - { - Deserialize(reader); - } - catch - { - return false; - } - return true; - } - } - - public ConsensusPayload MakeChangeView(ChangeViewReason reason) - { - return ChangeViewPayloads[MyIndex] = MakeSignedPayload(new ChangeView - { - Reason = reason, - Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS() - }); - } - - public ConsensusPayload MakeCommit() - { - return CommitPayloads[MyIndex] ?? (CommitPayloads[MyIndex] = MakeSignedPayload(new Commit - { - Signature = EnsureHeader().Sign(keyPair) - })); - } - - private ConsensusPayload MakeSignedPayload(ConsensusMessage message) - { - message.ViewNumber = ViewNumber; - ConsensusPayload payload = new ConsensusPayload - { - Version = Block.Version, - PrevHash = Block.PrevHash, - BlockIndex = Block.Index, - ValidatorIndex = (ushort)MyIndex, - ConsensusMessage = message - }; - SignPayload(payload); - return payload; - } - - private void SignPayload(ConsensusPayload payload) - { - ContractParametersContext sc; - try - { - sc = new ContractParametersContext(payload); - wallet.Sign(sc); - } - catch (InvalidOperationException) - { - return; - } - payload.Witness = sc.GetWitnesses()[0]; - } - - public ConsensusPayload MakePrepareRequest() - { - byte[] buffer = new byte[sizeof(ulong)]; - random.NextBytes(buffer); - Block.ConsensusData.Nonce = BitConverter.ToUInt64(buffer, 0); - - IEnumerable memoryPoolTransactions = Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions(); - foreach (IPolicyPlugin plugin in Plugin.Policies) - memoryPoolTransactions = plugin.FilterForBlock(memoryPoolTransactions); - - List transactions = memoryPoolTransactions.ToList(); - +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Neo.Consensus +{ + internal class ConsensusContext : IDisposable, ISerializable + { + /// + /// Prefix for saving consensus state. + /// + public const byte CN_Context = 0xf4; + + public Block Block; + public byte ViewNumber; + public ECPoint[] Validators; + public int MyIndex; + public UInt256[] TransactionHashes; + public Dictionary Transactions; + public ConsensusPayload[] PreparationPayloads; + public ConsensusPayload[] CommitPayloads; + public ConsensusPayload[] ChangeViewPayloads; + 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 Snapshot Snapshot { get; private set; } + private KeyPair keyPair; + private readonly Wallet wallet; + private readonly Store store; + private readonly Random random = new Random(); + + public int F => (Validators.Length - 1) / 3; + public int M => Validators.Length - F; + public bool IsPrimary => MyIndex == Block.ConsensusData.PrimaryIndex; + public bool IsBackup => MyIndex >= 0 && MyIndex != Block.ConsensusData.PrimaryIndex; + 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)); + + #region Consensus States + public bool RequestSentOrReceived => PreparationPayloads[Block.ConsensusData.PrimaryIndex] != null; + public bool ResponseSent => !WatchOnly && PreparationPayloads[MyIndex] != null; + public bool CommitSent => !WatchOnly && CommitPayloads[MyIndex] != null; + public bool BlockSent => Block.Transactions != null; + public bool ViewChanging => !WatchOnly && ChangeViewPayloads[MyIndex]?.GetDeserializedMessage().NewViewNumber > ViewNumber; + public bool NotAcceptingPayloadsDueToViewChanging => ViewChanging && !MoreThanFNodesCommittedOrLost; + // A possible attack can happen if the last node to commit is malicious and either sends change view after his + // commit to stall nodes in a higher view, or if he refuses to send recovery messages. In addition, if a node + // asking change views loses network or crashes and comes back when nodes are committed in more than one higher + // numbered view, it is possible for the node accepting recovery to commit in any of the higher views, thus + // potentially splitting nodes among views and stalling the network. + public bool MoreThanFNodesCommittedOrLost => (CountCommitted + CountFailed) > F; + #endregion + + public int Size => throw new NotImplementedException(); + + public ConsensusContext(Wallet wallet, Store store) + { + this.wallet = wallet; + this.store = store; + } + + public Block CreateBlock() + { + Contract contract = Contract.CreateMultiSigContract(M, Validators); + ContractParametersContext sc = new ContractParametersContext(Block); + for (int i = 0, j = 0; i < Validators.Length && j < M; i++) + { + if (CommitPayloads[i]?.ConsensusMessage.ViewNumber != ViewNumber) continue; + sc.AddSignature(contract, Validators[i], CommitPayloads[i].GetDeserializedMessage().Signature); + j++; + } + Block.Witness = sc.GetWitnesses()[0]; + Block.Transactions = TransactionHashes.Select(p => Transactions[p]).ToArray(); + return Block; + } + + public void Deserialize(BinaryReader reader) + { + Reset(0); + if (reader.ReadUInt32() != Block.Version) throw new FormatException(); + if (reader.ReadUInt32() != Block.Index) throw new InvalidOperationException(); + Block.Timestamp = reader.ReadUInt64(); + Block.NextConsensus = reader.ReadSerializable(); + if (Block.NextConsensus.Equals(UInt160.Zero)) + Block.NextConsensus = null; + Block.ConsensusData = reader.ReadSerializable(); + ViewNumber = reader.ReadByte(); + TransactionHashes = reader.ReadSerializableArray(); + if (TransactionHashes.Length == 0) + TransactionHashes = null; + Transaction[] transactions = reader.ReadSerializableArray(Block.MaxTransactionsPerBlock); + Transactions = transactions.Length == 0 ? null : transactions.ToDictionary(p => p.Hash); + PreparationPayloads = new ConsensusPayload[reader.ReadVarInt(Blockchain.MaxValidators)]; + for (int i = 0; i < PreparationPayloads.Length; i++) + PreparationPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; + CommitPayloads = new ConsensusPayload[reader.ReadVarInt(Blockchain.MaxValidators)]; + for (int i = 0; i < CommitPayloads.Length; i++) + CommitPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; + ChangeViewPayloads = new ConsensusPayload[reader.ReadVarInt(Blockchain.MaxValidators)]; + for (int i = 0; i < ChangeViewPayloads.Length; i++) + ChangeViewPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; + LastChangeViewPayloads = new ConsensusPayload[reader.ReadVarInt(Blockchain.MaxValidators)]; + for (int i = 0; i < LastChangeViewPayloads.Length; i++) + LastChangeViewPayloads[i] = reader.ReadBoolean() ? reader.ReadSerializable() : null; + } + + public void Dispose() + { + Snapshot?.Dispose(); + } + + public Block EnsureHeader() + { + if (TransactionHashes == null) return null; + if (Block.MerkleRoot is null) + Block.MerkleRoot = Block.CalculateMerkleRoot(Block.ConsensusData.Hash, TransactionHashes); + return Block; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetPrimaryIndex(byte viewNumber) + { + int p = ((int)Block.Index - viewNumber) % Validators.Length; + return p >= 0 ? (uint)p : (uint)(p + Validators.Length); + } + + public bool Load() + { + byte[] data = store.Get(CN_Context, new byte[0]); + if (data is null || data.Length == 0) return false; + using (MemoryStream ms = new MemoryStream(data, false)) + using (BinaryReader reader = new BinaryReader(ms)) + { + try + { + Deserialize(reader); + } + catch + { + return false; + } + return true; + } + } + + public ConsensusPayload MakeChangeView(ChangeViewReason reason) + { + return ChangeViewPayloads[MyIndex] = MakeSignedPayload(new ChangeView + { + Reason = reason, + Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS() + }); + } + + public ConsensusPayload MakeCommit() + { + return CommitPayloads[MyIndex] ?? (CommitPayloads[MyIndex] = MakeSignedPayload(new Commit + { + Signature = EnsureHeader().Sign(keyPair) + })); + } + + private ConsensusPayload MakeSignedPayload(ConsensusMessage message) + { + message.ViewNumber = ViewNumber; + ConsensusPayload payload = new ConsensusPayload + { + Version = Block.Version, + PrevHash = Block.PrevHash, + BlockIndex = Block.Index, + ValidatorIndex = (ushort)MyIndex, + ConsensusMessage = message + }; + SignPayload(payload); + return payload; + } + + private void SignPayload(ConsensusPayload payload) + { + ContractParametersContext sc; + try + { + sc = new ContractParametersContext(payload); + wallet.Sign(sc); + } + catch (InvalidOperationException) + { + return; + } + payload.Witness = sc.GetWitnesses()[0]; + } + + public ConsensusPayload MakePrepareRequest() + { + byte[] buffer = new byte[sizeof(ulong)]; + random.NextBytes(buffer); + Block.ConsensusData.Nonce = BitConverter.ToUInt64(buffer, 0); + + IEnumerable memoryPoolTransactions = Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions(); + foreach (IPolicyPlugin plugin in Plugin.Policies) + memoryPoolTransactions = plugin.FilterForBlock(memoryPoolTransactions); + + List transactions = memoryPoolTransactions.ToList(); + // Limit Speaker proposal to the limit `MaxTransactionsPerBlock` or all available transactions of the mempool + TransactionHashes = new UInt256[Math.Min(transactions.Count, NativeContract.Policy.GetMaxTransactionsPerBlock(Snapshot))]; + Transactions = new Dictionary(); + // Prevent that block exceed the max size + Block.Transactions = new Transaction[0]; uint maxBlockSize = NativeContract.Policy.GetMaxBlockSize(Snapshot); - TransactionHashes = new UInt256[Math.Min(transactions.Count, NativeContract.Policy.GetMaxTransactionsPerBlock(Snapshot))]; - - // Prevent that block exceed the max size - - Block.Transactions = new Transaction[0]; - var fixedSize = Block.Size + IO.Helper.GetVarSize(TransactionHashes.Length); // ensure that the var size grows without exceed the max size - - for (int x = 0, max = TransactionHashes.Length; x < max; x++) - { - var tx = transactions[x]; - - // Check if exceed - if (fixedSize + UInt256.Length + tx.Size > maxBlockSize) break; - - TransactionHashes[x] = tx.Hash; - Transactions.Add(tx.Hash, tx); - } - - // Truncate null values - + // Ensure that the var size grows without exceed the max size + var fixedSize = Block.Size + IO.Helper.GetVarSize(TransactionHashes.Length); + for (int x = 0, max = TransactionHashes.Length; x < max; x++) + { + var tx = transactions[x]; + + // Check if maximum block size has been already exceeded with the current selected set + if (fixedSize + UInt256.Length + tx.Size > maxBlockSize) break; + + TransactionHashes[x] = tx.Hash; + Transactions.Add(tx.Hash, tx); + } + + // Truncate null values if (TransactionHashes.Length > Transactions.Count) + Array.Resize(ref TransactionHashes, Transactions.Count); + + Block.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestampMS(), PrevHeader.Timestamp + 1); + return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareRequest + { + Timestamp = Block.Timestamp, + Nonce = Block.ConsensusData.Nonce, + TransactionHashes = TransactionHashes + }); + } + + public ConsensusPayload MakeRecoveryRequest() + { + return MakeSignedPayload(new RecoveryRequest + { + Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS() + }); + } + + public ConsensusPayload MakeRecoveryMessage() + { + PrepareRequest prepareRequestMessage = null; + if (TransactionHashes != null) + { + prepareRequestMessage = new PrepareRequest + { + ViewNumber = ViewNumber, + Timestamp = Block.Timestamp, + Nonce = Block.ConsensusData.Nonce, + TransactionHashes = TransactionHashes + }; + } + return MakeSignedPayload(new RecoveryMessage() + { + ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null).Select(p => RecoveryMessage.ChangeViewPayloadCompact.FromPayload(p)).Take(M).ToDictionary(p => (int)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).GroupBy(p => p.GetDeserializedMessage().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).Select(p => RecoveryMessage.PreparationPayloadCompact.FromPayload(p)).ToDictionary(p => (int)p.ValidatorIndex), + CommitMessages = CommitSent + ? CommitPayloads.Where(p => p != null).Select(p => RecoveryMessage.CommitPayloadCompact.FromPayload(p)).ToDictionary(p => (int)p.ValidatorIndex) + : new Dictionary() + }); + } + + public ConsensusPayload MakePrepareResponse() + { + return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareResponse + { + PreparationHash = PreparationPayloads[Block.ConsensusData.PrimaryIndex].Hash + }); + } + + public void Reset(byte viewNumber) + { + if (viewNumber == 0) + { + Snapshot?.Dispose(); + Snapshot = Blockchain.Singleton.GetSnapshot(); + Block = new Block + { + PrevHash = Snapshot.CurrentBlockHash, + Index = Snapshot.Height + 1, + NextConsensus = Blockchain.GetConsensusAddress(NativeContract.NEO.GetValidators(Snapshot).ToArray()), + ConsensusData = new ConsensusData() + }; + Validators = NativeContract.NEO.GetNextBlockValidators(Snapshot); + MyIndex = -1; + ChangeViewPayloads = new ConsensusPayload[Validators.Length]; + LastChangeViewPayloads = new ConsensusPayload[Validators.Length]; + CommitPayloads = new ConsensusPayload[Validators.Length]; + if (LastSeenMessage == null) + { + LastSeenMessage = new int[Validators.Length]; + for (int i = 0; i < Validators.Length; i++) + LastSeenMessage[i] = -1; + } + keyPair = null; + for (int i = 0; i < Validators.Length; i++) + { + WalletAccount account = wallet?.GetAccount(Validators[i]); + if (account?.HasKey != true) continue; + MyIndex = i; + keyPair = account.GetKey(); + break; + } + } + else + { + for (int i = 0; i < LastChangeViewPayloads.Length; i++) + if (ChangeViewPayloads[i]?.GetDeserializedMessage().NewViewNumber >= viewNumber) + LastChangeViewPayloads[i] = ChangeViewPayloads[i]; + else + LastChangeViewPayloads[i] = null; + } + ViewNumber = viewNumber; + Block.ConsensusData.PrimaryIndex = GetPrimaryIndex(viewNumber); + Block.MerkleRoot = null; + Block.Timestamp = 0; + Block.Transactions = null; + TransactionHashes = null; + PreparationPayloads = new ConsensusPayload[Validators.Length]; + if (MyIndex >= 0) LastSeenMessage[MyIndex] = (int)Block.Index; + } + + public void Save() + { + store.PutSync(CN_Context, new byte[0], this.ToArray()); + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(Block.Version); + writer.Write(Block.Index); + writer.Write(Block.Timestamp); + writer.Write(Block.NextConsensus ?? UInt160.Zero); + writer.Write(Block.ConsensusData); + writer.Write(ViewNumber); + writer.Write(TransactionHashes ?? new UInt256[0]); + writer.Write(Transactions?.Values.ToArray() ?? new Transaction[0]); + writer.WriteVarInt(PreparationPayloads.Length); + foreach (var payload in PreparationPayloads) + { + bool hasPayload = !(payload is null); + writer.Write(hasPayload); + if (!hasPayload) continue; + writer.Write(payload); + } + writer.WriteVarInt(CommitPayloads.Length); + foreach (var payload in CommitPayloads) + { + bool hasPayload = !(payload is null); + writer.Write(hasPayload); + if (!hasPayload) continue; + writer.Write(payload); + } + writer.WriteVarInt(ChangeViewPayloads.Length); + foreach (var payload in ChangeViewPayloads) + { + bool hasPayload = !(payload is null); + writer.Write(hasPayload); + if (!hasPayload) continue; + writer.Write(payload); + } + writer.WriteVarInt(LastChangeViewPayloads.Length); + foreach (var payload in LastChangeViewPayloads) { - Array.Resize(ref TransactionHashes, Transactions.Count); - } - - // Create valid request - - Block.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestampMS(), PrevHeader.Timestamp + 1); - return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareRequest - { - Timestamp = Block.Timestamp, - Nonce = Block.ConsensusData.Nonce, - TransactionHashes = TransactionHashes - }); - } - - public ConsensusPayload MakeRecoveryRequest() - { - return MakeSignedPayload(new RecoveryRequest - { - Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS() - }); - } - - public ConsensusPayload MakeRecoveryMessage() - { - PrepareRequest prepareRequestMessage = null; - if (TransactionHashes != null) - { - prepareRequestMessage = new PrepareRequest - { - ViewNumber = ViewNumber, - Timestamp = Block.Timestamp, - Nonce = Block.ConsensusData.Nonce, - TransactionHashes = TransactionHashes - }; - } - return MakeSignedPayload(new RecoveryMessage() - { - ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null).Select(p => RecoveryMessage.ChangeViewPayloadCompact.FromPayload(p)).Take(M).ToDictionary(p => (int)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).GroupBy(p => p.GetDeserializedMessage().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).Select(p => RecoveryMessage.PreparationPayloadCompact.FromPayload(p)).ToDictionary(p => (int)p.ValidatorIndex), - CommitMessages = CommitSent - ? CommitPayloads.Where(p => p != null).Select(p => RecoveryMessage.CommitPayloadCompact.FromPayload(p)).ToDictionary(p => (int)p.ValidatorIndex) - : new Dictionary() - }); - } - - public ConsensusPayload MakePrepareResponse() - { - return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareResponse - { - PreparationHash = PreparationPayloads[Block.ConsensusData.PrimaryIndex].Hash - }); - } - - public void Reset(byte viewNumber) - { - if (viewNumber == 0) - { - Snapshot?.Dispose(); - Snapshot = Blockchain.Singleton.GetSnapshot(); - Block = new Block - { - PrevHash = Snapshot.CurrentBlockHash, - Index = Snapshot.Height + 1, - NextConsensus = Blockchain.GetConsensusAddress(NativeContract.NEO.GetValidators(Snapshot).ToArray()), - ConsensusData = new ConsensusData() - }; - Validators = NativeContract.NEO.GetNextBlockValidators(Snapshot); - MyIndex = -1; - ChangeViewPayloads = new ConsensusPayload[Validators.Length]; - LastChangeViewPayloads = new ConsensusPayload[Validators.Length]; - CommitPayloads = new ConsensusPayload[Validators.Length]; - if (LastSeenMessage == null) - { - LastSeenMessage = new int[Validators.Length]; - for (int i = 0; i < Validators.Length; i++) - LastSeenMessage[i] = -1; - } - keyPair = null; - for (int i = 0; i < Validators.Length; i++) - { - WalletAccount account = wallet?.GetAccount(Validators[i]); - if (account?.HasKey != true) continue; - MyIndex = i; - keyPair = account.GetKey(); - break; - } - } - else - { - for (int i = 0; i < LastChangeViewPayloads.Length; i++) - if (ChangeViewPayloads[i]?.GetDeserializedMessage().NewViewNumber >= viewNumber) - LastChangeViewPayloads[i] = ChangeViewPayloads[i]; - else - LastChangeViewPayloads[i] = null; - } - ViewNumber = viewNumber; - Block.ConsensusData.PrimaryIndex = GetPrimaryIndex(viewNumber); - Block.MerkleRoot = null; - Block.Timestamp = 0; - Block.Transactions = null; - TransactionHashes = null; - PreparationPayloads = new ConsensusPayload[Validators.Length]; - if (MyIndex >= 0) LastSeenMessage[MyIndex] = (int)Block.Index; - } - - public void Save() - { - store.PutSync(CN_Context, new byte[0], this.ToArray()); - } - - public void Serialize(BinaryWriter writer) - { - writer.Write(Block.Version); - writer.Write(Block.Index); - writer.Write(Block.Timestamp); - writer.Write(Block.NextConsensus ?? UInt160.Zero); - writer.Write(Block.ConsensusData); - writer.Write(ViewNumber); - writer.Write(TransactionHashes ?? new UInt256[0]); - writer.Write(Transactions?.Values.ToArray() ?? new Transaction[0]); - writer.WriteVarInt(PreparationPayloads.Length); - foreach (var payload in PreparationPayloads) - { - bool hasPayload = !(payload is null); - writer.Write(hasPayload); - if (!hasPayload) continue; - writer.Write(payload); - } - writer.WriteVarInt(CommitPayloads.Length); - foreach (var payload in CommitPayloads) - { - bool hasPayload = !(payload is null); - writer.Write(hasPayload); - if (!hasPayload) continue; - writer.Write(payload); - } - writer.WriteVarInt(ChangeViewPayloads.Length); - foreach (var payload in ChangeViewPayloads) - { - bool hasPayload = !(payload is null); - writer.Write(hasPayload); - if (!hasPayload) continue; - writer.Write(payload); - } - writer.WriteVarInt(LastChangeViewPayloads.Length); - foreach (var payload in LastChangeViewPayloads) - { - bool hasPayload = !(payload is null); - writer.Write(hasPayload); - if (!hasPayload) continue; - writer.Write(payload); - } - } - } -} + bool hasPayload = !(payload is null); + writer.Write(hasPayload); + if (!hasPayload) continue; + writer.Write(payload); + } + } + } +}