From 1dd78a56d2e36a25b824529b325c0d9682ee44c8 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Sun, 10 Jan 2021 21:13:30 +0800 Subject: [PATCH 1/3] Remove consensus --- src/neo/Consensus/ChangeView.cs | 44 - src/neo/Consensus/ChangeViewReason.cs | 12 - src/neo/Consensus/Commit.cs | 26 - src/neo/Consensus/ConsensusContext.cs | 544 ---------- src/neo/Consensus/ConsensusMessage.cs | 55 - src/neo/Consensus/ConsensusMessageType.cs | 22 - src/neo/Consensus/ConsensusService.cs | 694 ------------ src/neo/Consensus/PrepareRequest.cs | 51 - src/neo/Consensus/PrepareResponse.cs | 29 - ...ecoveryMessage.ChangeViewPayloadCompact.cs | 41 - .../RecoveryMessage.CommitPayloadCompact.cs | 41 - ...coveryMessage.PreparationPayloadCompact.cs | 33 - src/neo/Consensus/RecoveryMessage.cs | 111 -- src/neo/Consensus/RecoveryRequest.cs | 31 - src/neo/NeoSystem.cs | 14 +- src/neo/Network/P2P/LocalNode.cs | 4 +- .../Network/P2P/RemoteNode.ProtocolHandler.cs | 11 +- src/neo/Network/P2P/TaskManager.cs | 8 +- src/neo/Persistence/Prefixes.cs | 5 - src/neo/Plugins/IConsensusProvider.cs | 7 + src/neo/SmartContract/Native/NeoToken.cs | 2 +- .../Consensus/UT_ChangeViewPayloadCompact.cs | 33 - tests/neo.UnitTests/Consensus/UT_Consensus.cs | 994 ------------------ .../Consensus/UT_ConsensusContext.cs | 193 ---- .../Consensus/UT_ConsensusServiceMailbox.cs | 47 - .../Consensus/UT_RecoveryRequest.cs | 29 - tests/neo.UnitTests/UT_NeoSystem.cs | 3 - 27 files changed, 19 insertions(+), 3065 deletions(-) delete mode 100644 src/neo/Consensus/ChangeView.cs delete mode 100644 src/neo/Consensus/ChangeViewReason.cs delete mode 100644 src/neo/Consensus/Commit.cs delete mode 100644 src/neo/Consensus/ConsensusContext.cs delete mode 100644 src/neo/Consensus/ConsensusMessage.cs delete mode 100644 src/neo/Consensus/ConsensusMessageType.cs delete mode 100644 src/neo/Consensus/ConsensusService.cs delete mode 100644 src/neo/Consensus/PrepareRequest.cs delete mode 100644 src/neo/Consensus/PrepareResponse.cs delete mode 100644 src/neo/Consensus/RecoveryMessage.ChangeViewPayloadCompact.cs delete mode 100644 src/neo/Consensus/RecoveryMessage.CommitPayloadCompact.cs delete mode 100644 src/neo/Consensus/RecoveryMessage.PreparationPayloadCompact.cs delete mode 100644 src/neo/Consensus/RecoveryMessage.cs delete mode 100644 src/neo/Consensus/RecoveryRequest.cs create mode 100644 src/neo/Plugins/IConsensusProvider.cs delete mode 100644 tests/neo.UnitTests/Consensus/UT_ChangeViewPayloadCompact.cs delete mode 100644 tests/neo.UnitTests/Consensus/UT_Consensus.cs delete mode 100644 tests/neo.UnitTests/Consensus/UT_ConsensusContext.cs delete mode 100644 tests/neo.UnitTests/Consensus/UT_ConsensusServiceMailbox.cs delete mode 100644 tests/neo.UnitTests/Consensus/UT_RecoveryRequest.cs diff --git a/src/neo/Consensus/ChangeView.cs b/src/neo/Consensus/ChangeView.cs deleted file mode 100644 index 607e44cfd5..0000000000 --- a/src/neo/Consensus/ChangeView.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.IO; - -namespace Neo.Consensus -{ - public class ChangeView : ConsensusMessage - { - /// - /// NewViewNumber is always set to the current ViewNumber asking changeview + 1 - /// - public byte NewViewNumber => (byte)(ViewNumber + 1); - - /// - /// Timestamp of when the ChangeView message was created. This allows receiving nodes to ensure - /// they only respond once to a specific ChangeView request (it thus prevents replay of the ChangeView - /// message from repeatedly broadcasting RecoveryMessages). - /// - public ulong Timestamp; - - /// - /// Reason - /// - public ChangeViewReason Reason; - - public override int Size => base.Size + - sizeof(ulong) + // Timestamp - sizeof(ChangeViewReason); // Reason - - public ChangeView() : base(ConsensusMessageType.ChangeView) { } - - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - Timestamp = reader.ReadUInt64(); - Reason = (ChangeViewReason)reader.ReadByte(); - } - - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - writer.Write(Timestamp); - writer.Write((byte)Reason); - } - } -} diff --git a/src/neo/Consensus/ChangeViewReason.cs b/src/neo/Consensus/ChangeViewReason.cs deleted file mode 100644 index 87c88c74c2..0000000000 --- a/src/neo/Consensus/ChangeViewReason.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Neo.Consensus -{ - public enum ChangeViewReason : byte - { - Timeout = 0x0, - ChangeAgreement = 0x1, - TxNotFound = 0x2, - TxRejectedByPolicy = 0x3, - TxInvalid = 0x4, - BlockRejectedByPolicy = 0x5 - } -} diff --git a/src/neo/Consensus/Commit.cs b/src/neo/Consensus/Commit.cs deleted file mode 100644 index fce9b8caaf..0000000000 --- a/src/neo/Consensus/Commit.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Neo.IO; -using System.IO; - -namespace Neo.Consensus -{ - public class Commit : ConsensusMessage - { - public byte[] Signature; - - public override int Size => base.Size + Signature.Length; - - public Commit() : base(ConsensusMessageType.Commit) { } - - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - Signature = reader.ReadFixedBytes(64); - } - - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - writer.Write(Signature); - } - } -} diff --git a/src/neo/Consensus/ConsensusContext.cs b/src/neo/Consensus/ConsensusContext.cs deleted file mode 100644 index 3101c2d54d..0000000000 --- a/src/neo/Consensus/ConsensusContext.cs +++ /dev/null @@ -1,544 +0,0 @@ -using Neo.Cryptography; -using Neo.Cryptography.ECC; -using Neo.IO; -using Neo.Ledger; -using Neo.Network.P2P.Payloads; -using Neo.Persistence; -using Neo.SmartContract; -using Neo.SmartContract.Native; -using Neo.VM; -using Neo.Wallets; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using static Neo.Consensus.RecoveryMessage; - -namespace Neo.Consensus -{ - public class ConsensusContext : IDisposable, ISerializable - { - /// - /// Key for saving consensus state. - /// - private const byte ConsensusStatePrefix = 0xf4; - - public Block Block; - public byte ViewNumber; - public ECPoint[] Validators; - public int MyIndex; - public UInt256[] TransactionHashes; - public Dictionary Transactions; - public ExtensiblePayload[] PreparationPayloads; - public ExtensiblePayload[] CommitPayloads; - public ExtensiblePayload[] ChangeViewPayloads; - public ExtensiblePayload[] 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; private set; } - - /// - /// Store all verified unsorted transactions' senders' fee currently in the consensus context. - /// - public TransactionVerificationContext VerificationContext = new TransactionVerificationContext(); - - public SnapshotView Snapshot { get; private set; } - private KeyPair keyPair; - private int _witnessSize; - private readonly Wallet wallet; - private readonly IStore store; - private Dictionary cachedMessages; - - 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 - { - get - { - if (LastSeenMessage == null) return 0; - return Validators.Count(p => !LastSeenMessage.TryGetValue(p, out var value) || value < (Block.Index - 1)); - } - } - 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; - public bool ResponseSent => !WatchOnly && PreparationPayloads[MyIndex] != null; - public bool CommitSent => !WatchOnly && CommitPayloads[MyIndex] != null; - public bool BlockSent => Block.Transactions != null; - public bool ViewChanging => !WatchOnly && GetMessage(ChangeViewPayloads[MyIndex])?.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, IStore store) - { - this.wallet = wallet; - this.store = store; - } - - public Block CreateBlock() - { - EnsureHeader(); - Contract contract = Contract.CreateMultiSigContract(M, Validators); - ContractParametersContext sc = new ContractParametersContext(Block); - 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); - j++; - } - Block.Witness = sc.GetWitnesses()[0]; - Block.Transactions = TransactionHashes.Select(p => Transactions[p]).ToArray(); - return Block; - } - - public ExtensiblePayload CreatePayload(ConsensusMessage message, byte[] invocationScript = null) - { - ExtensiblePayload payload = new ExtensiblePayload - { - Category = "Consensus", - ValidBlockStart = 0, - ValidBlockEnd = message.BlockIndex, - Sender = GetSender(message.ValidatorIndex), - Data = message.ToArray(), - Witness = invocationScript is null ? null : new Witness - { - InvocationScript = invocationScript, - VerificationScript = Contract.CreateSignatureRedeemScript(Validators[message.ValidatorIndex]) - } - }; - cachedMessages.TryAdd(payload.Hash, message); - return payload; - } - - 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(); - Transaction[] transactions = reader.ReadSerializableArray(Block.MaxTransactionsPerBlock); - PreparationPayloads = reader.ReadNullableArray(ProtocolSettings.Default.ValidatorsCount); - CommitPayloads = reader.ReadNullableArray(ProtocolSettings.Default.ValidatorsCount); - ChangeViewPayloads = reader.ReadNullableArray(ProtocolSettings.Default.ValidatorsCount); - LastChangeViewPayloads = reader.ReadNullableArray(ProtocolSettings.Default.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) - { - foreach (Transaction tx in Transactions.Values) - VerificationContext.AddTransaction(tx); - } - } - - 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; - } - - public ConsensusMessage GetMessage(ExtensiblePayload payload) - { - if (payload is null) return null; - if (!cachedMessages.TryGetValue(payload.Hash, out ConsensusMessage message)) - cachedMessages.Add(payload.Hash, message = ConsensusMessage.DeserializeFrom(payload.Data)); - return message; - } - - public T GetMessage(ExtensiblePayload payload) where T : ConsensusMessage - { - return (T)GetMessage(payload); - } - - private ChangeViewPayloadCompact GetChangeViewPayloadCompact(ExtensiblePayload payload) - { - ChangeView message = GetMessage(payload); - return new ChangeViewPayloadCompact - { - ValidatorIndex = message.ValidatorIndex, - OriginalViewNumber = message.ViewNumber, - Timestamp = message.Timestamp, - InvocationScript = payload.Witness.InvocationScript - }; - } - - private CommitPayloadCompact GetCommitPayloadCompact(ExtensiblePayload payload) - { - Commit message = GetMessage(payload); - return new CommitPayloadCompact - { - ViewNumber = message.ViewNumber, - ValidatorIndex = message.ValidatorIndex, - Signature = message.Signature, - InvocationScript = payload.Witness.InvocationScript - }; - } - - private PreparationPayloadCompact GetPreparationPayloadCompact(ExtensiblePayload payload) - { - return new PreparationPayloadCompact - { - ValidatorIndex = GetMessage(payload).ValidatorIndex, - InvocationScript = payload.Witness.InvocationScript - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetPrimaryIndex(byte viewNumber) - { - int p = ((int)Block.Index - viewNumber) % Validators.Length; - return p >= 0 ? (byte)p : (byte)(p + Validators.Length); - } - - public UInt160 GetSender(int index) - { - return Contract.CreateSignatureRedeemScript(Validators[index]).ToScriptHash(); - } - - public bool Load() - { - byte[] data = store.TryGet(ConsensusStatePrefix, null); - 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 ExtensiblePayload MakeChangeView(ChangeViewReason reason) - { - return ChangeViewPayloads[MyIndex] = MakeSignedPayload(new ChangeView - { - Reason = reason, - Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS() - }); - } - - public ExtensiblePayload MakeCommit() - { - return CommitPayloads[MyIndex] ?? (CommitPayloads[MyIndex] = MakeSignedPayload(new Commit - { - Signature = EnsureHeader().Sign(keyPair) - })); - } - - private ExtensiblePayload MakeSignedPayload(ConsensusMessage message) - { - message.BlockIndex = Block.Index; - message.ValidatorIndex = (byte)MyIndex; - message.ViewNumber = ViewNumber; - ExtensiblePayload payload = CreatePayload(message, null); - SignPayload(payload); - return payload; - } - - private void SignPayload(ExtensiblePayload payload) - { - ContractParametersContext sc; - try - { - sc = new ContractParametersContext(payload); - wallet.Sign(sc); - } - catch (InvalidOperationException) - { - return; - } - payload.Witness = sc.GetWitnesses()[0]; - } - - /// - /// Return the expected block size - /// - internal int GetExpectedBlockSize() - { - return GetExpectedBlockSizeWithoutTransactions(Transactions.Count) + // Base size - Transactions.Values.Sum(u => u.Size); // Sum Txs - } - - /// - /// Return the expected block system fee - /// - internal long GetExpectedBlockSystemFee() - { - return Transactions.Values.Sum(u => u.SystemFee); // Sum Txs - } - - /// - /// Return the expected block size without txs - /// - /// Expected transactions - internal int GetExpectedBlockSizeWithoutTransactions(int expectedTransactions) - { - var blockSize = - // BlockBase - sizeof(uint) + //Version - UInt256.Length + //PrevHash - UInt256.Length + //MerkleRoot - sizeof(ulong) + //Timestamp - sizeof(uint) + //Index - UInt160.Length + //NextConsensus - 1 + // - _witnessSize; //Witness - - blockSize += - // Block - Block.ConsensusData.Size + //ConsensusData - IO.Helper.GetVarSize(expectedTransactions + 1); //Transactions count - - return blockSize; - } - - /// - /// Prevent that block exceed the max size - /// - /// Ordered transactions - internal void EnsureMaxBlockLimitation(IEnumerable txs) - { - uint maxBlockSize = NativeContract.Policy.GetMaxBlockSize(Snapshot); - long maxBlockSystemFee = NativeContract.Policy.GetMaxBlockSystemFee(Snapshot); - uint maxTransactionsPerBlock = NativeContract.Policy.GetMaxTransactionsPerBlock(Snapshot); - - // Limit Speaker proposal to the limit `MaxTransactionsPerBlock` or all available transactions of the mempool - txs = txs.Take((int)maxTransactionsPerBlock); - List hashes = new List(); - Transactions = new Dictionary(); - VerificationContext = new TransactionVerificationContext(); - - // Expected block size - var blockSize = GetExpectedBlockSizeWithoutTransactions(txs.Count()); - var blockSystemFee = 0L; - - // Iterate transaction until reach the size or maximum system fee - foreach (Transaction tx in txs) - { - // Check if maximum block size has been already exceeded with the current selected set - blockSize += tx.Size; - if (blockSize > maxBlockSize) break; - - // Check if maximum block system fee has been already exceeded with the current selected set - blockSystemFee += tx.SystemFee; - if (blockSystemFee > maxBlockSystemFee) break; - - hashes.Add(tx.Hash); - Transactions.Add(tx.Hash, tx); - VerificationContext.AddTransaction(tx); - } - - TransactionHashes = hashes.ToArray(); - } - - public ExtensiblePayload MakePrepareRequest() - { - var random = new Random(); - Span buffer = stackalloc byte[sizeof(ulong)]; - random.NextBytes(buffer); - Block.ConsensusData.Nonce = BitConverter.ToUInt64(buffer); - EnsureMaxBlockLimitation(Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions()); - Block.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestampMS(), PrevHeader.Timestamp + 1); - - return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareRequest - { - Version = Block.Version, - PrevHash = Block.PrevHash, - Timestamp = Block.Timestamp, - Nonce = Block.ConsensusData.Nonce, - TransactionHashes = TransactionHashes - }); - } - - public ExtensiblePayload MakeRecoveryRequest() - { - return MakeSignedPayload(new RecoveryRequest - { - Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS() - }); - } - - public ExtensiblePayload MakeRecoveryMessage() - { - PrepareRequest prepareRequestMessage = null; - if (TransactionHashes != null) - { - prepareRequestMessage = new PrepareRequest - { - Version = Block.Version, - PrevHash = Block.PrevHash, - ViewNumber = ViewNumber, - Timestamp = Block.Timestamp, - BlockIndex = Block.Index, - Nonce = Block.ConsensusData.Nonce, - TransactionHashes = TransactionHashes - }; - } - return MakeSignedPayload(new RecoveryMessage() - { - ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null).Select(p => GetChangeViewPayloadCompact(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 => 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).Select(p => GetPreparationPayloadCompact(p)).ToDictionary(p => (int)p.ValidatorIndex), - CommitMessages = CommitSent - ? CommitPayloads.Where(p => p != null).Select(p => GetCommitPayloadCompact(p)).ToDictionary(p => (int)p.ValidatorIndex) - : new Dictionary() - }); - } - - public ExtensiblePayload 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.ShouldRefreshCommittee(Snapshot.Height + 1) ? - NativeContract.NEO.ComputeNextBlockValidators(Snapshot) : - NativeContract.NEO.GetNextBlockValidators(Snapshot)) - }; - var pv = Validators; - Validators = NativeContract.NEO.GetNextBlockValidators(Snapshot); - if (_witnessSize == 0 || (pv != null && pv.Length != Validators.Length)) - { - // Compute the expected size of the witness - using (ScriptBuilder sb = new ScriptBuilder()) - { - for (int x = 0; x < M; x++) - { - sb.EmitPush(new byte[64]); - } - _witnessSize = new Witness - { - InvocationScript = sb.ToArray(), - VerificationScript = Contract.CreateMultiSigRedeemScript(M, Validators) - }.Size; - } - } - 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; - LastSeenMessage = new Dictionary(); - foreach (var validator in Validators) - { - if (previous_last_seen_message != null && previous_last_seen_message.TryGetValue(validator, out var value)) - LastSeenMessage[validator] = value; - else - LastSeenMessage[validator] = Snapshot.Height; - } - } - 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; - } - cachedMessages = new Dictionary(); - } - else - { - for (int i = 0; i < LastChangeViewPayloads.Length; i++) - if (GetMessage(ChangeViewPayloads[i])?.NewViewNumber >= viewNumber) - LastChangeViewPayloads[i] = ChangeViewPayloads[i]; - else - LastChangeViewPayloads[i] = null; - } - ViewNumber = viewNumber; - Block.ConsensusData = new ConsensusData - { - PrimaryIndex = GetPrimaryIndex(viewNumber) - }; - Block.MerkleRoot = null; - Block.Timestamp = 0; - Block.Transactions = null; - TransactionHashes = null; - PreparationPayloads = new ExtensiblePayload[Validators.Length]; - if (MyIndex >= 0) LastSeenMessage[Validators[MyIndex]] = Block.Index; - } - - public void Save() - { - store.PutSync(ConsensusStatePrefix, null, 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.WriteNullableArray(PreparationPayloads); - writer.WriteNullableArray(CommitPayloads); - writer.WriteNullableArray(ChangeViewPayloads); - writer.WriteNullableArray(LastChangeViewPayloads); - } - } -} diff --git a/src/neo/Consensus/ConsensusMessage.cs b/src/neo/Consensus/ConsensusMessage.cs deleted file mode 100644 index fceae7073a..0000000000 --- a/src/neo/Consensus/ConsensusMessage.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Neo.IO; -using Neo.IO.Caching; -using System; -using System.IO; - -namespace Neo.Consensus -{ - public abstract class ConsensusMessage : ISerializable - { - public readonly ConsensusMessageType Type; - public uint BlockIndex; - public byte ValidatorIndex; - public byte ViewNumber; - - public virtual int Size => - sizeof(ConsensusMessageType) + //Type - sizeof(uint) + //BlockIndex - sizeof(byte) + //ValidatorIndex - sizeof(byte); //ViewNumber - - protected ConsensusMessage(ConsensusMessageType type) - { - if (!Enum.IsDefined(typeof(ConsensusMessageType), type)) - throw new ArgumentOutOfRangeException(nameof(type)); - this.Type = type; - } - - public virtual void Deserialize(BinaryReader reader) - { - if (Type != (ConsensusMessageType)reader.ReadByte()) - throw new FormatException(); - BlockIndex = reader.ReadUInt32(); - ValidatorIndex = reader.ReadByte(); - if (ValidatorIndex >= ProtocolSettings.Default.ValidatorsCount) - throw new FormatException(); - ViewNumber = reader.ReadByte(); - } - - public static ConsensusMessage DeserializeFrom(byte[] data) - { - ConsensusMessageType type = (ConsensusMessageType)data[0]; - ISerializable message = ReflectionCache.CreateSerializable(type, data); - if (message is null) throw new FormatException(); - return (ConsensusMessage)message; - } - - public virtual void Serialize(BinaryWriter writer) - { - writer.Write((byte)Type); - writer.Write(BlockIndex); - writer.Write(ValidatorIndex); - writer.Write(ViewNumber); - } - } -} diff --git a/src/neo/Consensus/ConsensusMessageType.cs b/src/neo/Consensus/ConsensusMessageType.cs deleted file mode 100644 index fe13207db1..0000000000 --- a/src/neo/Consensus/ConsensusMessageType.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Neo.IO.Caching; - -namespace Neo.Consensus -{ - public enum ConsensusMessageType : byte - { - [ReflectionCache(typeof(ChangeView))] - ChangeView = 0x00, - - [ReflectionCache(typeof(PrepareRequest))] - PrepareRequest = 0x20, - [ReflectionCache(typeof(PrepareResponse))] - PrepareResponse = 0x21, - [ReflectionCache(typeof(Commit))] - Commit = 0x30, - - [ReflectionCache(typeof(RecoveryRequest))] - RecoveryRequest = 0x40, - [ReflectionCache(typeof(RecoveryMessage))] - RecoveryMessage = 0x41, - } -} diff --git a/src/neo/Consensus/ConsensusService.cs b/src/neo/Consensus/ConsensusService.cs deleted file mode 100644 index 3361315e06..0000000000 --- a/src/neo/Consensus/ConsensusService.cs +++ /dev/null @@ -1,694 +0,0 @@ -using Akka.Actor; -using Akka.Configuration; -using Neo.Cryptography; -using Neo.IO; -using Neo.IO.Actors; -using Neo.Ledger; -using Neo.Network.P2P; -using Neo.Network.P2P.Payloads; -using Neo.Persistence; -using Neo.SmartContract; -using Neo.SmartContract.Native; -using Neo.Wallets; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Neo.Consensus -{ - public sealed class ConsensusService : UntypedActor - { - public class Start { public bool IgnoreRecoveryLogs; } - public class SetViewNumber { public byte ViewNumber; } - internal class Timer { public uint Height; public byte ViewNumber; } - - private readonly ConsensusContext context; - private readonly IActorRef localNode; - private readonly IActorRef taskManager; - private readonly IActorRef blockchain; - private ICancelable timer_token; - private DateTime block_received_time; - private uint block_received_index; - private bool started = false; - - /// - /// This will record the information from last scheduled timer - /// - private DateTime clock_started = TimeProvider.Current.UtcNow; - private TimeSpan expected_delay = TimeSpan.Zero; - - /// - /// This will be cleared every block (so it will not grow out of control, but is used to prevent repeatedly - /// responding to the same message. - /// - private readonly HashSet knownHashes = new HashSet(); - /// - /// This variable is only true during OnRecoveryMessageReceived - /// - private bool isRecovering = false; - - public ConsensusService(IActorRef localNode, IActorRef taskManager, IActorRef blockchain, IStore store, Wallet wallet) - : this(localNode, taskManager, blockchain, new ConsensusContext(wallet, store)) - { - } - - internal ConsensusService(IActorRef localNode, IActorRef taskManager, IActorRef blockchain, ConsensusContext context) - { - this.localNode = localNode; - this.taskManager = taskManager; - this.blockchain = blockchain; - this.context = context; - Context.System.EventStream.Subscribe(Self, typeof(Blockchain.PersistCompleted)); - Context.System.EventStream.Subscribe(Self, typeof(Blockchain.RelayResult)); - } - - private bool AddTransaction(Transaction tx, bool verify) - { - if (verify) - { - VerifyResult result = tx.Verify(context.Snapshot, context.VerificationContext); - if (result == VerifyResult.PolicyFail) - { - Log($"reject tx: {tx.Hash}{Environment.NewLine}{tx.ToArray().ToHexString()}", LogLevel.Warning); - RequestChangeView(ChangeViewReason.TxRejectedByPolicy); - return false; - } - else if (result != VerifyResult.Succeed) - { - Log($"Invalid transaction: {tx.Hash}{Environment.NewLine}{tx.ToArray().ToHexString()}", LogLevel.Warning); - RequestChangeView(ChangeViewReason.TxInvalid); - return false; - } - } - context.Transactions[tx.Hash] = tx; - context.VerificationContext.AddTransaction(tx); - return CheckPrepareResponse(); - } - - private bool CheckPrepareResponse() - { - if (context.TransactionHashes.Length == context.Transactions.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; - - // Check maximum block size via Native Contract policy - if (context.GetExpectedBlockSize() > NativeContract.Policy.GetMaxBlockSize(context.Snapshot)) - { - Log($"rejected block: {context.Block.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() > NativeContract.Policy.GetMaxBlockSystemFee(context.Snapshot)) - { - Log($"rejected block: {context.Block.Index} The system fee exceed the policy", LogLevel.Warning); - RequestChangeView(ChangeViewReason.BlockRejectedByPolicy); - return false; - } - - // Timeout extension due to prepare response sent - // around 2*15/M=30.0/5 ~ 40% block time (for M=5) - ExtendTimerByFactor(2); - - Log($"send prepare response"); - localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakePrepareResponse() }); - CheckPreparations(); - } - return true; - } - - private void ChangeTimer(TimeSpan delay) - { - clock_started = TimeProvider.Current.UtcNow; - expected_delay = delay; - timer_token.CancelIfNotNull(); - timer_token = Context.System.Scheduler.ScheduleTellOnceCancelable(delay, Self, new Timer - { - Height = context.Block.Index, - ViewNumber = context.ViewNumber - }, ActorRefs.NoSender); - } - - private void CheckCommits() - { - if (context.CommitPayloads.Count(p => context.GetMessage(p)?.ViewNumber == context.ViewNumber) >= context.M && context.TransactionHashes.All(p => context.Transactions.ContainsKey(p))) - { - block_received_index = context.Block.Index; - block_received_time = TimeProvider.Current.UtcNow; - Block block = context.CreateBlock(); - Log($"relay block: height={block.Index} hash={block.Hash} tx={block.Transactions.Length}"); - blockchain.Tell(block); - } - } - - private void CheckExpectedView(byte viewNumber) - { - if (context.ViewNumber >= viewNumber) return; - var messages = context.ChangeViewPayloads.Select(p => context.GetMessage(p)).ToArray(); - // if there are `M` change view payloads with NewViewNumber greater than viewNumber, then, it is safe to move - if (messages.Count(p => p != null && p.NewViewNumber >= viewNumber) >= context.M) - { - if (!context.WatchOnly) - { - ChangeView message = messages[context.MyIndex]; - // Communicate the network about my agreement to move to `viewNumber` - // if my last change view payload, `message`, has NewViewNumber lower than current view to change - if (message is null || message.NewViewNumber < viewNumber) - localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeChangeView(ChangeViewReason.ChangeAgreement) }); - } - InitializeConsensus(viewNumber); - } - } - - private void CheckPreparations() - { - if (context.PreparationPayloads.Count(p => p != null) >= context.M && context.TransactionHashes.All(p => context.Transactions.ContainsKey(p))) - { - ExtensiblePayload payload = context.MakeCommit(); - Log($"send commit"); - 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(Blockchain.MillisecondsPerBlock)); - CheckCommits(); - } - } - - private void InitializeConsensus(byte viewNumber) - { - context.Reset(viewNumber); - if (viewNumber > 0) - Log($"changeview: view={viewNumber} primary={context.Validators[context.GetPrimaryIndex((byte)(viewNumber - 1u))]}", LogLevel.Warning); - Log($"initialize: height={context.Block.Index} view={viewNumber} index={context.MyIndex} role={(context.IsPrimary ? "Primary" : context.WatchOnly ? "WatchOnly" : "Backup")}"); - if (context.WatchOnly) return; - if (context.IsPrimary) - { - if (isRecovering) - { - ChangeTimer(TimeSpan.FromMilliseconds(Blockchain.MillisecondsPerBlock << (viewNumber + 1))); - } - else - { - TimeSpan span = Blockchain.TimePerBlock; - if (block_received_index + 1 == context.Block.Index) - { - var diff = TimeProvider.Current.UtcNow - block_received_time; - if (diff >= span) - span = TimeSpan.Zero; - else - span -= diff; - } - ChangeTimer(span); - } - } - else - { - ChangeTimer(TimeSpan.FromMilliseconds(Blockchain.MillisecondsPerBlock << (viewNumber + 1))); - } - } - - private void Log(string message, LogLevel level = LogLevel.Info) - { - Utility.Log(nameof(ConsensusService), level, message); - } - - private void OnChangeViewReceived(ExtensiblePayload payload, ChangeView message) - { - if (message.NewViewNumber <= context.ViewNumber) - OnRecoveryRequestReceived(payload, message); - - if (context.CommitSent) return; - - var expectedView = context.GetMessage(context.ChangeViewPayloads[message.ValidatorIndex])?.NewViewNumber ?? 0; - if (message.NewViewNumber <= expectedView) - return; - - Log($"{nameof(OnChangeViewReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex} nv={message.NewViewNumber} reason={message.Reason}"); - context.ChangeViewPayloads[message.ValidatorIndex] = payload; - CheckExpectedView(message.NewViewNumber); - } - - private void OnCommitReceived(ExtensiblePayload payload, Commit commit) - { - ref ExtensiblePayload existingCommitPayload = ref context.CommitPayloads[commit.ValidatorIndex]; - if (existingCommitPayload != null) - { - if (existingCommitPayload.Hash != payload.Hash) - Log($"{nameof(OnCommitReceived)}: different commit from validator! height={commit.BlockIndex} index={commit.ValidatorIndex} view={commit.ViewNumber} existingView={context.GetMessage(existingCommitPayload).ViewNumber}", LogLevel.Warning); - return; - } - - // Timeout extension: commit has been received with success - // around 4*15s/M=60.0s/5=12.0s ~ 80% block time (for M=5) - ExtendTimerByFactor(4); - - if (commit.ViewNumber == context.ViewNumber) - { - Log($"{nameof(OnCommitReceived)}: height={commit.BlockIndex} view={commit.ViewNumber} index={commit.ValidatorIndex} nc={context.CountCommitted} nf={context.CountFailed}"); - - byte[] hashData = context.EnsureHeader()?.GetHashData(); - if (hashData == null) - { - existingCommitPayload = payload; - } - else if (Crypto.VerifySignature(hashData, commit.Signature, context.Validators[commit.ValidatorIndex])) - { - existingCommitPayload = payload; - CheckCommits(); - } - return; - } - // Receiving commit from another view - Log($"{nameof(OnCommitReceived)}: record commit for different view={commit.ViewNumber} index={commit.ValidatorIndex} height={commit.BlockIndex}"); - existingCommitPayload = payload; - } - - // this function increases existing timer (never decreases) with a value proportional to `maxDelayInBlockTimes`*`Blockchain.MillisecondsPerBlock` - private void ExtendTimerByFactor(int maxDelayInBlockTimes) - { - TimeSpan nextDelay = expected_delay - (TimeProvider.Current.UtcNow - clock_started) + TimeSpan.FromMilliseconds(maxDelayInBlockTimes * Blockchain.MillisecondsPerBlock / (double)context.M); - if (!context.WatchOnly && !context.ViewChanging && !context.CommitSent && (nextDelay > TimeSpan.Zero)) - ChangeTimer(nextDelay); - } - - private void OnConsensusPayload(ExtensiblePayload payload) - { - if (context.BlockSent) return; - ConsensusMessage message; - try - { - message = context.GetMessage(payload); - } - catch (FormatException) - { - return; - } - catch (IOException) - { - return; - } - if (message.BlockIndex != context.Block.Index) - { - if (context.Block.Index < message.BlockIndex) - { - Log($"chain sync: expected={message.BlockIndex} current={context.Block.Index - 1} nodes={LocalNode.Singleton.ConnectedCount}", LogLevel.Warning); - } - return; - } - if (message.ValidatorIndex >= context.Validators.Length) return; - if (payload.Sender != Contract.CreateSignatureRedeemScript(context.Validators[message.ValidatorIndex]).ToScriptHash()) return; - context.LastSeenMessage[context.Validators[message.ValidatorIndex]] = message.BlockIndex; - switch (message) - { - case ChangeView view: - OnChangeViewReceived(payload, view); - break; - case PrepareRequest request: - OnPrepareRequestReceived(payload, request); - break; - case PrepareResponse response: - OnPrepareResponseReceived(payload, response); - break; - case Commit commit: - OnCommitReceived(payload, commit); - break; - case RecoveryRequest request: - OnRecoveryRequestReceived(payload, request); - break; - case RecoveryMessage recovery: - OnRecoveryMessageReceived(recovery); - break; - } - } - - private void OnPersistCompleted(Block block) - { - Log($"persist block: height={block.Index} hash={block.Hash} tx={block.Transactions.Length}"); - knownHashes.Clear(); - InitializeConsensus(0); - } - - private void OnRecoveryMessageReceived(RecoveryMessage message) - { - // isRecovering is always set to false again after OnRecoveryMessageReceived - isRecovering = true; - int validChangeViews = 0, totalChangeViews = 0, validPrepReq = 0, totalPrepReq = 0; - int validPrepResponses = 0, totalPrepResponses = 0, validCommits = 0, totalCommits = 0; - - Log($"{nameof(OnRecoveryMessageReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex}"); - try - { - if (message.ViewNumber > context.ViewNumber) - { - if (context.CommitSent) return; - ExtensiblePayload[] changeViewPayloads = message.GetChangeViewPayloads(context); - totalChangeViews = changeViewPayloads.Length; - foreach (ExtensiblePayload changeViewPayload in changeViewPayloads) - if (ReverifyAndProcessPayload(changeViewPayload)) validChangeViews++; - } - if (message.ViewNumber == context.ViewNumber && !context.NotAcceptingPayloadsDueToViewChanging && !context.CommitSent) - { - if (!context.RequestSentOrReceived) - { - ExtensiblePayload prepareRequestPayload = message.GetPrepareRequestPayload(context); - if (prepareRequestPayload != null) - { - totalPrepReq = 1; - if (ReverifyAndProcessPayload(prepareRequestPayload)) validPrepReq++; - } - else if (context.IsPrimary) - SendPrepareRequest(); - } - ExtensiblePayload[] prepareResponsePayloads = message.GetPrepareResponsePayloads(context); - totalPrepResponses = prepareResponsePayloads.Length; - foreach (ExtensiblePayload prepareResponsePayload in prepareResponsePayloads) - if (ReverifyAndProcessPayload(prepareResponsePayload)) validPrepResponses++; - } - if (message.ViewNumber <= context.ViewNumber) - { - // Ensure we know about all commits from lower view numbers. - ExtensiblePayload[] commitPayloads = message.GetCommitPayloadsFromRecoveryMessage(context); - totalCommits = commitPayloads.Length; - foreach (ExtensiblePayload commitPayload in commitPayloads) - if (ReverifyAndProcessPayload(commitPayload)) validCommits++; - } - } - finally - { - Log($"{nameof(OnRecoveryMessageReceived)}: finished (valid/total) " + - $"ChgView: {validChangeViews}/{totalChangeViews} " + - $"PrepReq: {validPrepReq}/{totalPrepReq} " + - $"PrepResp: {validPrepResponses}/{totalPrepResponses} " + - $"Commits: {validCommits}/{totalCommits}"); - isRecovering = false; - } - } - - private void OnRecoveryRequestReceived(ExtensiblePayload payload, ConsensusMessage message) - { - // We keep track of the payload hashes received in this block, and don't respond with recovery - // in response to the same payload that we already responded to previously. - // ChangeView messages include a Timestamp when the change view is sent, thus if a node restarts - // and issues a change view for the same view, it will have a different hash and will correctly respond - // again; however replay attacks of the ChangeView message from arbitrary nodes will not trigger an - // additional recovery message response. - if (!knownHashes.Add(payload.Hash)) return; - - Log($"On{message.GetType().Name}Received: height={message.BlockIndex} index={message.ValidatorIndex} view={message.ViewNumber}"); - if (context.WatchOnly) return; - if (!context.CommitSent) - { - bool shouldSendRecovery = false; - int allowedRecoveryNodeCount = context.F; - // Limit recoveries to be sent from an upper limit of `f` nodes - for (int i = 1; i <= allowedRecoveryNodeCount; i++) - { - var chosenIndex = (message.ValidatorIndex + i) % context.Validators.Length; - if (chosenIndex != context.MyIndex) continue; - shouldSendRecovery = true; - break; - } - - if (!shouldSendRecovery) return; - } - Log($"send recovery: view={context.ViewNumber}"); - localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeRecoveryMessage() }); - } - - private void OnPrepareRequestReceived(ExtensiblePayload payload, PrepareRequest message) - { - if (context.RequestSentOrReceived || context.NotAcceptingPayloadsDueToViewChanging) return; - if (message.ValidatorIndex != context.Block.ConsensusData.PrimaryIndex || message.ViewNumber != context.ViewNumber) return; - if (message.Version != context.Block.Version || message.PrevHash != context.Block.PrevHash) return; - Log($"{nameof(OnPrepareRequestReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex} tx={message.TransactionHashes.Length}"); - if (message.Timestamp <= context.PrevHeader.Timestamp || message.Timestamp > TimeProvider.Current.UtcNow.AddMilliseconds(8 * Blockchain.MillisecondsPerBlock).ToTimestampMS()) - { - Log($"Timestamp incorrect: {message.Timestamp}", LogLevel.Warning); - return; - } - if (message.TransactionHashes.Any(p => context.Snapshot.ContainsTransaction(p))) - { - Log($"Invalid request: transaction already exists", LogLevel.Warning); - return; - } - - // Timeout extension: prepare request has been received with success - // around 2*15/M=30.0/5 ~ 40% block time (for M=5) - ExtendTimerByFactor(2); - - context.Block.Timestamp = message.Timestamp; - context.Block.ConsensusData.Nonce = message.Nonce; - context.TransactionHashes = 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().GetHashData(); - 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, context.Validators[i])) - context.CommitPayloads[i] = null; - - if (context.TransactionHashes.Length == 0) - { - // There are no tx so we should act like if all the transactions were filled - CheckPrepareResponse(); - return; - } - - Dictionary mempoolVerified = Blockchain.Singleton.MemPool.GetVerifiedTransactions().ToDictionary(p => p.Hash); - List unverified = new List(); - foreach (UInt256 hash in context.TransactionHashes) - { - if (mempoolVerified.TryGetValue(hash, out Transaction tx)) - { - if (!AddTransaction(tx, false)) - return; - } - else - { - if (Blockchain.Singleton.MemPool.TryGetValue(hash, out tx)) - unverified.Add(tx); - } - } - foreach (Transaction tx in unverified) - if (!AddTransaction(tx, true)) - return; - if (context.Transactions.Count < context.TransactionHashes.Length) - { - UInt256[] hashes = context.TransactionHashes.Where(i => !context.Transactions.ContainsKey(i)).ToArray(); - taskManager.Tell(new TaskManager.RestartTasks - { - Payload = InvPayload.Create(InventoryType.TX, hashes) - }); - } - } - - 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.ConsensusData.PrimaryIndex] != null && !message.PreparationHash.Equals(context.PreparationPayloads[context.Block.ConsensusData.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; - if (context.WatchOnly || context.CommitSent) return; - if (context.RequestSentOrReceived) - CheckPreparations(); - } - - protected override void OnReceive(object message) - { - if (message is Start options) - { - if (started) return; - OnStart(options); - } - else - { - if (!started) return; - switch (message) - { - case SetViewNumber setView: - InitializeConsensus(setView.ViewNumber); - break; - case Timer timer: - OnTimer(timer); - break; - case Transaction transaction: - OnTransaction(transaction); - break; - case Blockchain.PersistCompleted completed: - OnPersistCompleted(completed.Block); - break; - case Blockchain.RelayResult rr: - if (rr.Result == VerifyResult.Succeed && rr.Inventory is ExtensiblePayload payload && payload.Category == "Consensus") - OnConsensusPayload(payload); - break; - } - } - } - - private void RequestRecovery() - { - if (context.Block.Index == Blockchain.Singleton.HeaderHeight + 1) - localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeRecoveryRequest() }); - } - - private void OnStart(Start options) - { - Log("OnStart"); - started = true; - if (!options.IgnoreRecoveryLogs && context.Load()) - { - if (context.Transactions != null) - { - Sender.Ask(new Blockchain.FillMemoryPool - { - Transactions = context.Transactions.Values - }).Wait(); - } - if (context.CommitSent) - { - CheckPreparations(); - return; - } - } - InitializeConsensus(0); - // Issue a ChangeView with NewViewNumber of 0 to request recovery messages on start-up. - if (!context.WatchOnly) - RequestRecovery(); - } - - private void OnTimer(Timer timer) - { - if (context.WatchOnly || context.BlockSent) return; - if (timer.Height != context.Block.Index || timer.ViewNumber != context.ViewNumber) return; - Log($"timeout: height={timer.Height} view={timer.ViewNumber}"); - if (context.IsPrimary && !context.RequestSentOrReceived) - { - SendPrepareRequest(); - } - else if ((context.IsPrimary && context.RequestSentOrReceived) || context.IsBackup) - { - if (context.CommitSent) - { - // Re-send commit periodically by sending recover message in case of a network issue. - Log($"send recovery to resend commit"); - localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeRecoveryMessage() }); - ChangeTimer(TimeSpan.FromMilliseconds(Blockchain.MillisecondsPerBlock << 1)); - } - else - { - var reason = ChangeViewReason.Timeout; - - if (context.Block != null && context.TransactionHashes?.Length > context.Transactions?.Count) - { - reason = ChangeViewReason.TxNotFound; - } - - RequestChangeView(reason); - } - } - } - - private void OnTransaction(Transaction transaction) - { - if (!context.IsBackup || context.NotAcceptingPayloadsDueToViewChanging || !context.RequestSentOrReceived || context.ResponseSent || context.BlockSent) - return; - if (context.Transactions.ContainsKey(transaction.Hash)) return; - if (!context.TransactionHashes.Contains(transaction.Hash)) return; - AddTransaction(transaction, true); - } - - protected override void PostStop() - { - Log("OnStop"); - started = false; - Context.System.EventStream.Unsubscribe(Self); - context.Dispose(); - base.PostStop(); - } - - public static Props Props(IActorRef localNode, IActorRef taskManager, IActorRef blockchain, IStore store, Wallet wallet) - { - return Akka.Actor.Props.Create(() => new ConsensusService(localNode, taskManager, blockchain, store, wallet)).WithMailbox("consensus-service-mailbox"); - } - - private void RequestChangeView(ChangeViewReason reason) - { - if (context.WatchOnly) return; - // Request for next view is always one view more than the current context.ViewNumber - // Nodes will not contribute for changing to a view higher than (context.ViewNumber+1), unless they are recovered - // The latter may happen by nodes in higher views with, at least, `M` proofs - byte expectedView = context.ViewNumber; - expectedView++; - ChangeTimer(TimeSpan.FromMilliseconds(Blockchain.MillisecondsPerBlock << (expectedView + 1))); - if ((context.CountCommitted + context.CountFailed) > context.F) - { - Log($"skip requesting change view: height={context.Block.Index} view={context.ViewNumber} nv={expectedView} nc={context.CountCommitted} nf={context.CountFailed} reason={reason}"); - RequestRecovery(); - return; - } - Log($"request change view: height={context.Block.Index} view={context.ViewNumber} nv={expectedView} nc={context.CountCommitted} nf={context.CountFailed} reason={reason}"); - localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeChangeView(reason) }); - CheckExpectedView(expectedView); - } - - private bool ReverifyAndProcessPayload(ExtensiblePayload payload) - { - if (!payload.Verify(context.Snapshot)) return false; - OnConsensusPayload(payload); - return true; - } - - private void SendPrepareRequest() - { - Log($"send prepare request: height={context.Block.Index} view={context.ViewNumber}"); - localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakePrepareRequest() }); - - if (context.Validators.Length == 1) - CheckPreparations(); - - if (context.TransactionHashes.Length > 0) - { - foreach (InvPayload payload in InvPayload.CreateGroup(InventoryType.TX, context.TransactionHashes)) - localNode.Tell(Message.Create(MessageCommand.Inv, payload)); - } - ChangeTimer(TimeSpan.FromMilliseconds((Blockchain.MillisecondsPerBlock << (context.ViewNumber + 1)) - (context.ViewNumber == 0 ? Blockchain.MillisecondsPerBlock : 0))); - } - } - - internal class ConsensusServiceMailbox : PriorityMailbox - { - public ConsensusServiceMailbox(Akka.Actor.Settings settings, Config config) - : base(settings, config) - { - } - - internal protected override bool IsHighPriority(object message) - { - switch (message) - { - case ExtensiblePayload _: - case ConsensusService.SetViewNumber _: - case ConsensusService.Timer _: - case Blockchain.PersistCompleted _: - return true; - default: - return false; - } - } - } -} diff --git a/src/neo/Consensus/PrepareRequest.cs b/src/neo/Consensus/PrepareRequest.cs deleted file mode 100644 index 9e96ca4202..0000000000 --- a/src/neo/Consensus/PrepareRequest.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Neo.IO; -using Neo.Network.P2P.Payloads; -using System; -using System.IO; -using System.Linq; - -namespace Neo.Consensus -{ - public class PrepareRequest : ConsensusMessage - { - public uint Version; - public UInt256 PrevHash; - public ulong Timestamp; - public ulong Nonce; - public UInt256[] TransactionHashes; - - public override int Size => base.Size - + sizeof(uint) //Version - + UInt256.Length //PrevHash - + sizeof(ulong) //Timestamp - + sizeof(ulong) //Nonce - + TransactionHashes.GetVarSize(); //TransactionHashes - - public PrepareRequest() - : base(ConsensusMessageType.PrepareRequest) - { - } - - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - Version = reader.ReadUInt32(); - PrevHash = reader.ReadSerializable(); - Timestamp = reader.ReadUInt64(); - Nonce = reader.ReadUInt64(); - TransactionHashes = reader.ReadSerializableArray(Block.MaxTransactionsPerBlock); - if (TransactionHashes.Distinct().Count() != TransactionHashes.Length) - throw new FormatException(); - } - - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - writer.Write(Version); - writer.Write(PrevHash); - writer.Write(Timestamp); - writer.Write(Nonce); - writer.Write(TransactionHashes); - } - } -} diff --git a/src/neo/Consensus/PrepareResponse.cs b/src/neo/Consensus/PrepareResponse.cs deleted file mode 100644 index 7c2956ccc2..0000000000 --- a/src/neo/Consensus/PrepareResponse.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Neo.IO; -using System.IO; - -namespace Neo.Consensus -{ - public class PrepareResponse : ConsensusMessage - { - public UInt256 PreparationHash; - - public override int Size => base.Size + PreparationHash.Size; - - public PrepareResponse() - : base(ConsensusMessageType.PrepareResponse) - { - } - - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - PreparationHash = reader.ReadSerializable(); - } - - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - writer.Write(PreparationHash); - } - } -} diff --git a/src/neo/Consensus/RecoveryMessage.ChangeViewPayloadCompact.cs b/src/neo/Consensus/RecoveryMessage.ChangeViewPayloadCompact.cs deleted file mode 100644 index 8419193de6..0000000000 --- a/src/neo/Consensus/RecoveryMessage.ChangeViewPayloadCompact.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Neo.IO; -using System; -using System.IO; - -namespace Neo.Consensus -{ - partial class RecoveryMessage - { - public class ChangeViewPayloadCompact : ISerializable - { - public byte ValidatorIndex; - public byte OriginalViewNumber; - public ulong Timestamp; - public byte[] InvocationScript; - - int ISerializable.Size => - sizeof(byte) + //ValidatorIndex - sizeof(byte) + //OriginalViewNumber - sizeof(ulong) + //Timestamp - InvocationScript.GetVarSize(); //InvocationScript - - void ISerializable.Deserialize(BinaryReader reader) - { - ValidatorIndex = reader.ReadByte(); - if (ValidatorIndex >= ProtocolSettings.Default.ValidatorsCount) - throw new FormatException(); - OriginalViewNumber = reader.ReadByte(); - Timestamp = reader.ReadUInt64(); - InvocationScript = reader.ReadVarBytes(1024); - } - - void ISerializable.Serialize(BinaryWriter writer) - { - writer.Write(ValidatorIndex); - writer.Write(OriginalViewNumber); - writer.Write(Timestamp); - writer.WriteVarBytes(InvocationScript); - } - } - } -} diff --git a/src/neo/Consensus/RecoveryMessage.CommitPayloadCompact.cs b/src/neo/Consensus/RecoveryMessage.CommitPayloadCompact.cs deleted file mode 100644 index 81b3bc37af..0000000000 --- a/src/neo/Consensus/RecoveryMessage.CommitPayloadCompact.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Neo.IO; -using System; -using System.IO; - -namespace Neo.Consensus -{ - partial class RecoveryMessage - { - public class CommitPayloadCompact : ISerializable - { - public byte ViewNumber; - public byte ValidatorIndex; - public byte[] Signature; - public byte[] InvocationScript; - - int ISerializable.Size => - sizeof(byte) + //ViewNumber - sizeof(byte) + //ValidatorIndex - Signature.Length + //Signature - InvocationScript.GetVarSize(); //InvocationScript - - void ISerializable.Deserialize(BinaryReader reader) - { - ViewNumber = reader.ReadByte(); - ValidatorIndex = reader.ReadByte(); - if (ValidatorIndex >= ProtocolSettings.Default.ValidatorsCount) - throw new FormatException(); - Signature = reader.ReadFixedBytes(64); - InvocationScript = reader.ReadVarBytes(1024); - } - - void ISerializable.Serialize(BinaryWriter writer) - { - writer.Write(ViewNumber); - writer.Write(ValidatorIndex); - writer.Write(Signature); - writer.WriteVarBytes(InvocationScript); - } - } - } -} diff --git a/src/neo/Consensus/RecoveryMessage.PreparationPayloadCompact.cs b/src/neo/Consensus/RecoveryMessage.PreparationPayloadCompact.cs deleted file mode 100644 index 72fbd2de2f..0000000000 --- a/src/neo/Consensus/RecoveryMessage.PreparationPayloadCompact.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Neo.IO; -using System; -using System.IO; - -namespace Neo.Consensus -{ - partial class RecoveryMessage - { - public class PreparationPayloadCompact : ISerializable - { - public byte ValidatorIndex; - public byte[] InvocationScript; - - int ISerializable.Size => - sizeof(byte) + //ValidatorIndex - InvocationScript.GetVarSize(); //InvocationScript - - void ISerializable.Deserialize(BinaryReader reader) - { - ValidatorIndex = reader.ReadByte(); - if (ValidatorIndex >= ProtocolSettings.Default.ValidatorsCount) - throw new FormatException(); - InvocationScript = reader.ReadVarBytes(1024); - } - - void ISerializable.Serialize(BinaryWriter writer) - { - writer.Write(ValidatorIndex); - writer.WriteVarBytes(InvocationScript); - } - } - } -} diff --git a/src/neo/Consensus/RecoveryMessage.cs b/src/neo/Consensus/RecoveryMessage.cs deleted file mode 100644 index 11b8bd0361..0000000000 --- a/src/neo/Consensus/RecoveryMessage.cs +++ /dev/null @@ -1,111 +0,0 @@ -using Neo.IO; -using Neo.Network.P2P.Payloads; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Neo.Consensus -{ - public partial class RecoveryMessage : ConsensusMessage - { - public 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 Dictionary PreparationMessages; - public Dictionary CommitMessages; - - public override int Size => base.Size - + /* ChangeViewMessages */ ChangeViewMessages?.Values.GetVarSize() ?? 0 - + /* PrepareRequestMessage */ 1 + PrepareRequestMessage?.Size ?? 0 - + /* PreparationHash */ PreparationHash?.Size ?? 0 - + /* PreparationMessages */ PreparationMessages?.Values.GetVarSize() ?? 0 - + /* CommitMessages */ CommitMessages?.Values.GetVarSize() ?? 0; - - public RecoveryMessage() : base(ConsensusMessageType.RecoveryMessage) - { - } - - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - ChangeViewMessages = reader.ReadSerializableArray(ProtocolSettings.Default.ValidatorsCount).ToDictionary(p => (int)p.ValidatorIndex); - if (reader.ReadBoolean()) - PrepareRequestMessage = reader.ReadSerializable(); - else - { - int preparationHashSize = UInt256.Zero.Size; - if (preparationHashSize == (int)reader.ReadVarInt((ulong)preparationHashSize)) - PreparationHash = new UInt256(reader.ReadFixedBytes(preparationHashSize)); - } - - PreparationMessages = reader.ReadSerializableArray(ProtocolSettings.Default.ValidatorsCount).ToDictionary(p => (int)p.ValidatorIndex); - CommitMessages = reader.ReadSerializableArray(ProtocolSettings.Default.ValidatorsCount).ToDictionary(p => (int)p.ValidatorIndex); - } - - internal ExtensiblePayload[] GetChangeViewPayloads(ConsensusContext context) - { - return ChangeViewMessages.Values.Select(p => context.CreatePayload(new ChangeView - { - BlockIndex = BlockIndex, - ValidatorIndex = p.ValidatorIndex, - ViewNumber = p.OriginalViewNumber, - Timestamp = p.Timestamp - }, p.InvocationScript)).ToArray(); - } - - internal ExtensiblePayload[] GetCommitPayloadsFromRecoveryMessage(ConsensusContext context) - { - return CommitMessages.Values.Select(p => context.CreatePayload(new Commit - { - BlockIndex = BlockIndex, - ValidatorIndex = p.ValidatorIndex, - ViewNumber = p.ViewNumber, - Signature = p.Signature - }, p.InvocationScript)).ToArray(); - } - - internal ExtensiblePayload GetPrepareRequestPayload(ConsensusContext context) - { - if (PrepareRequestMessage == null) return null; - if (!PreparationMessages.TryGetValue(context.Block.ConsensusData.PrimaryIndex, out PreparationPayloadCompact compact)) - return null; - return context.CreatePayload(PrepareRequestMessage, compact.InvocationScript); - } - - internal ExtensiblePayload[] GetPrepareResponsePayloads(ConsensusContext context) - { - UInt256 preparationHash = PreparationHash ?? context.PreparationPayloads[context.Block.ConsensusData.PrimaryIndex]?.Hash; - if (preparationHash is null) return Array.Empty(); - return PreparationMessages.Values.Where(p => p.ValidatorIndex != context.Block.ConsensusData.PrimaryIndex).Select(p => context.CreatePayload(new PrepareResponse - { - BlockIndex = BlockIndex, - ValidatorIndex = p.ValidatorIndex, - ViewNumber = ViewNumber, - PreparationHash = preparationHash - }, p.InvocationScript)).ToArray(); - } - - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - writer.Write(ChangeViewMessages.Values.ToArray()); - bool hasPrepareRequestMessage = PrepareRequestMessage != null; - writer.Write(hasPrepareRequestMessage); - if (hasPrepareRequestMessage) - writer.Write(PrepareRequestMessage); - else - { - if (PreparationHash == null) - writer.WriteVarInt(0); - else - writer.WriteVarBytes(PreparationHash.ToArray()); - } - - writer.Write(PreparationMessages.Values.ToArray()); - writer.Write(CommitMessages.Values.ToArray()); - } - } -} diff --git a/src/neo/Consensus/RecoveryRequest.cs b/src/neo/Consensus/RecoveryRequest.cs deleted file mode 100644 index 6ea0d7c2f7..0000000000 --- a/src/neo/Consensus/RecoveryRequest.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.IO; - -namespace Neo.Consensus -{ - public class RecoveryRequest : ConsensusMessage - { - /// - /// Timestamp of when the ChangeView message was created. This allows receiving nodes to ensure - /// they only respond once to a specific RecoveryRequest request. - /// In this sense, it prevents replay of the RecoveryRequest message from the repeatedly broadcast of Recovery's messages. - /// - public ulong Timestamp; - - public override int Size => base.Size - + sizeof(ulong); //Timestamp - - public RecoveryRequest() : base(ConsensusMessageType.RecoveryRequest) { } - - public override void Deserialize(BinaryReader reader) - { - base.Deserialize(reader); - Timestamp = reader.ReadUInt64(); - } - - public override void Serialize(BinaryWriter writer) - { - base.Serialize(writer); - writer.Write(Timestamp); - } - } -} diff --git a/src/neo/NeoSystem.cs b/src/neo/NeoSystem.cs index 76980ca319..9ba6eb202d 100644 --- a/src/neo/NeoSystem.cs +++ b/src/neo/NeoSystem.cs @@ -1,10 +1,8 @@ using Akka.Actor; -using Neo.Consensus; using Neo.Ledger; using Neo.Network.P2P; using Neo.Persistence; using Neo.Plugins; -using Neo.Wallets; using System; namespace Neo @@ -15,12 +13,10 @@ public class NeoSystem : IDisposable $"akka {{ log-dead-letters = off , loglevel = warning, loggers = [ \"{typeof(Utility.Logger).AssemblyQualifiedName}\" ] }}" + $"blockchain-mailbox {{ mailbox-type: \"{typeof(BlockchainMailbox).AssemblyQualifiedName}\" }}" + $"task-manager-mailbox {{ mailbox-type: \"{typeof(TaskManagerMailbox).AssemblyQualifiedName}\" }}" + - $"remote-node-mailbox {{ mailbox-type: \"{typeof(RemoteNodeMailbox).AssemblyQualifiedName}\" }}" + - $"consensus-service-mailbox {{ mailbox-type: \"{typeof(ConsensusServiceMailbox).AssemblyQualifiedName}\" }}"); + $"remote-node-mailbox {{ mailbox-type: \"{typeof(RemoteNodeMailbox).AssemblyQualifiedName}\" }}"); public IActorRef Blockchain { get; } public IActorRef LocalNode { get; } - internal IActorRef TaskManager { get; } - public IActorRef Consensus { get; private set; } + public IActorRef TaskManager { get; } private readonly string storage_engine; private readonly IStore store; @@ -86,12 +82,6 @@ internal void ResumeNodeStartup() } } - public void StartConsensus(Wallet wallet, IStore consensus_store = null, bool ignoreRecoveryLogs = false) - { - Consensus = ActorSystem.ActorOf(ConsensusService.Props(this.LocalNode, this.TaskManager, this.Blockchain, consensus_store ?? store, wallet)); - Consensus.Tell(new ConsensusService.Start { IgnoreRecoveryLogs = ignoreRecoveryLogs }, Blockchain); - } - public void StartNode(ChannelsConfig config) { start_message = config; diff --git a/src/neo/Network/P2P/LocalNode.cs b/src/neo/Network/P2P/LocalNode.cs index c8bc937001..c2941c38bd 100644 --- a/src/neo/Network/P2P/LocalNode.cs +++ b/src/neo/Network/P2P/LocalNode.cs @@ -15,8 +15,8 @@ namespace Neo.Network.P2P { public class LocalNode : Peer { - internal class RelayDirectly { public IInventory Inventory; } - internal class SendDirectly { public IInventory Inventory; } + public class RelayDirectly { public IInventory Inventory; } + public class SendDirectly { public IInventory Inventory; } public const uint ProtocolVersion = 0; private const int MaxCountFromSeedList = 5; diff --git a/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs b/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs index b0b1051c71..7d85875c56 100644 --- a/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs +++ b/src/neo/Network/P2P/RemoteNode.ProtocolHandler.cs @@ -288,15 +288,10 @@ private void OnGetHeadersMessageReceived(GetBlockByIndexPayload payload) private void OnInventoryReceived(IInventory inventory) { pendingKnownHashes.Remove(inventory.Hash); - switch (inventory) + if (inventory is Block block) { - case Transaction transaction: - system.Consensus?.Tell(transaction); - break; - case Block block: - if (block.Index > Blockchain.Singleton.Height + InvPayload.MaxHashesCount) return; - UpdateLastBlockIndex(block.Index, false); - break; + if (block.Index > Blockchain.Singleton.Height + InvPayload.MaxHashesCount) return; + UpdateLastBlockIndex(block.Index, false); } knownHashes.Add(inventory.Hash); system.TaskManager.Tell(inventory); diff --git a/src/neo/Network/P2P/TaskManager.cs b/src/neo/Network/P2P/TaskManager.cs index 07088a9fb5..ef6e09e443 100644 --- a/src/neo/Network/P2P/TaskManager.cs +++ b/src/neo/Network/P2P/TaskManager.cs @@ -13,11 +13,11 @@ namespace Neo.Network.P2P { - internal class TaskManager : UntypedActor + public class TaskManager : UntypedActor { - public class Register { public VersionPayload Version; } - public class Update { public uint LastBlockIndex; public bool RequestTasks; } - public class NewTasks { public InvPayload Payload; } + internal class Register { public VersionPayload Version; } + internal class Update { public uint LastBlockIndex; public bool RequestTasks; } + internal class NewTasks { public InvPayload Payload; } public class RestartTasks { public InvPayload Payload; } private class Timer { } diff --git a/src/neo/Persistence/Prefixes.cs b/src/neo/Persistence/Prefixes.cs index fcd7549325..3b63665326 100644 --- a/src/neo/Persistence/Prefixes.cs +++ b/src/neo/Persistence/Prefixes.cs @@ -10,10 +10,5 @@ internal static class Prefixes public const byte IX_HeaderHashList = 0x80; public const byte IX_CurrentBlock = 0xc0; public const byte IX_CurrentHeader = 0xc1; - - /* Prefixes 0xf0 to 0xff are reserved for external use. - * - * Note: The saved consensus state uses the Prefix 0xf4 - */ } } diff --git a/src/neo/Plugins/IConsensusProvider.cs b/src/neo/Plugins/IConsensusProvider.cs new file mode 100644 index 0000000000..f9885da754 --- /dev/null +++ b/src/neo/Plugins/IConsensusProvider.cs @@ -0,0 +1,7 @@ +namespace Neo.Plugins +{ + public interface IConsensusProvider + { + void Start(); + } +} diff --git a/src/neo/SmartContract/Native/NeoToken.cs b/src/neo/SmartContract/Native/NeoToken.cs index 375cb628ef..64971dc640 100644 --- a/src/neo/SmartContract/Native/NeoToken.cs +++ b/src/neo/SmartContract/Native/NeoToken.cs @@ -303,7 +303,7 @@ private CachedCommittee GetCommitteeFromCache(StoreView snapshot) return snapshot.Storages[CreateStorageKey(Prefix_Committee)].GetInteroperable(); } - internal ECPoint[] ComputeNextBlockValidators(StoreView snapshot) + public ECPoint[] ComputeNextBlockValidators(StoreView snapshot) { return ComputeCommitteeMembers(snapshot).Select(p => p.PublicKey).Take(ProtocolSettings.Default.ValidatorsCount).OrderBy(p => p).ToArray(); } diff --git a/tests/neo.UnitTests/Consensus/UT_ChangeViewPayloadCompact.cs b/tests/neo.UnitTests/Consensus/UT_ChangeViewPayloadCompact.cs deleted file mode 100644 index 757d041801..0000000000 --- a/tests/neo.UnitTests/Consensus/UT_ChangeViewPayloadCompact.cs +++ /dev/null @@ -1,33 +0,0 @@ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Consensus; -using Neo.IO; - -namespace Neo.UnitTests.Consensus -{ - [TestClass] - public class UT_ChangeViewPayloadCompact - { - [TestMethod] - public void Size_Get() - { - var test = new RecoveryMessage.ChangeViewPayloadCompact() { Timestamp = 1, ValidatorIndex = 1, InvocationScript = new byte[0], OriginalViewNumber = 1 }; - ((ISerializable)test).Size.Should().Be(11); - - test = new RecoveryMessage.ChangeViewPayloadCompact() { Timestamp = 1, ValidatorIndex = 1, InvocationScript = new byte[1024], OriginalViewNumber = 1 }; - ((ISerializable)test).Size.Should().Be(1037); - } - - [TestMethod] - public void DeserializeAndSerialize() - { - var test = new RecoveryMessage.ChangeViewPayloadCompact() { Timestamp = 1, ValidatorIndex = 2, InvocationScript = new byte[] { 1, 2, 3 }, OriginalViewNumber = 3 }; - var clone = test.ToArray().AsSerializable(); - - Assert.AreEqual(test.Timestamp, clone.Timestamp); - Assert.AreEqual(test.ValidatorIndex, clone.ValidatorIndex); - Assert.AreEqual(test.OriginalViewNumber, clone.OriginalViewNumber); - CollectionAssert.AreEqual(test.InvocationScript, clone.InvocationScript); - } - } -} diff --git a/tests/neo.UnitTests/Consensus/UT_Consensus.cs b/tests/neo.UnitTests/Consensus/UT_Consensus.cs deleted file mode 100644 index a900f90797..0000000000 --- a/tests/neo.UnitTests/Consensus/UT_Consensus.cs +++ /dev/null @@ -1,994 +0,0 @@ -using Akka.Actor; -using Akka.TestKit; -using Akka.TestKit.Xunit2; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using Neo.Consensus; -using Neo.Cryptography; -using Neo.IO; -using Neo.Ledger; -using Neo.Network.P2P; -using Neo.Network.P2P.Payloads; -using Neo.SmartContract; -using Neo.SmartContract.Native; -using Neo.UnitTests.Cryptography; -using Neo.Wallets; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Reflection; -using System.Security.Cryptography; -using static Neo.SmartContract.Native.NeoToken; -using ECPoint = Neo.Cryptography.ECC.ECPoint; - -namespace Neo.UnitTests.Consensus -{ - [TestClass] - public class ConsensusTests : TestKit - { - private KeyPair[] kp_array; - - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - - var moked = new ECPoint[] { - ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", Neo.Cryptography.ECC.ECCurve.Secp256r1) - }; - - kp_array = new KeyPair[7] - { - UT_Crypto.generateKey(32), // not used, kept for index consistency, didactically - UT_Crypto.generateKey(32), - UT_Crypto.generateKey(32), - UT_Crypto.generateKey(32), - UT_Crypto.generateKey(32), - UT_Crypto.generateKey(32), - UT_Crypto.generateKey(32) - }.OrderBy(p => p.PublicKey).ToArray(); - - TestBlockchain.AddWhiteList(TestBlockchain.DefaultExtensibleWitnessWhiteList - .Concat(moked.Select(u => Contract.CreateSignatureContract(u).ScriptHash)) - .Concat(kp_array.Select(u => Contract.CreateSignatureContract(u.PublicKey).ScriptHash)) - .ToArray()); - } - - [TestCleanup] - public void Cleanup() - { - TestBlockchain.AddWhiteList(TestBlockchain.DefaultExtensibleWitnessWhiteList); - Shutdown(); - } - - [TestMethod] - public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Commits() - { - var mockWallet = new Mock(); - mockWallet.Setup(p => p.GetAccount(It.IsAny())).Returns(p => new TestWalletAccount(p)); - Console.WriteLine($"\n(UT-Consensus) Wallet is: {mockWallet.Object.GetAccount(UInt160.Zero).GetKey().PublicKey}"); - - var mockContext = new Mock(mockWallet.Object, Blockchain.Singleton.Store); - var timeValues = new[] { - new DateTime(1980, 06, 01, 0, 0, 1, 001, DateTimeKind.Utc), // For tests, used below - new DateTime(1980, 06, 01, 0, 0, 3, 001, DateTimeKind.Utc), // For receiving block - new DateTime(1980, 05, 01, 0, 0, 5, 001, DateTimeKind.Utc), // For Initialize - new DateTime(1980, 06, 01, 0, 0, 15, 001, DateTimeKind.Utc), // unused - }; - for (int i = 0; i < timeValues.Length; i++) - Console.WriteLine($"time {i}: {timeValues[i]} "); - ulong defaultTimestamp = 328665601001; // GMT: Sunday, June 1, 1980 12:00:01.001 AM - // check basic ConsensusContext - // mockConsensusContext.Object.block_received_time.ToTimestamp().Should().Be(4244941697); //1968-06-01 00:00:01 - // ============================================================================ - // creating ConsensusService actor - // ============================================================================ - - int timeIndex = 0; - var timeMock = new Mock(); - timeMock.SetupGet(tp => tp.UtcNow).Returns(() => timeValues[timeIndex]); - //.Callback(() => timeIndex = timeIndex + 1); //Comment while index is not fixed - - TimeProvider.Current = timeMock.Object; - TimeProvider.Current.UtcNow.ToTimestampMS().Should().Be(defaultTimestamp); //1980-06-01 00:00:15:001 - - //public void Log(string message, LogLevel level) - //create ILogPlugin for Tests - /* - mockConsensusContext.Setup(mr => mr.Log(It.IsAny(), It.IsAny())) - .Callback((string message, LogLevel level) => { - Console.WriteLine($"CONSENSUS LOG: {message}"); - } - ); - */ - - // Creating a test block - Header header = new Header(); - TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out uint indexVal, out Witness scriptVal); - header.Size.Should().Be(105); - Console.WriteLine($"header {header} hash {header.Hash} {header.PrevHash} timestamp {timestampVal}"); - timestampVal.Should().Be(defaultTimestamp); - TestProbe subscriber = CreateTestProbe(); - TestActorRef actorConsensus = ActorOfAsTestActorRef( - Akka.Actor.Props.Create(() => (ConsensusService)Activator.CreateInstance(typeof(ConsensusService), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { subscriber, subscriber, subscriber, mockContext.Object }, null)) - ); - - var testPersistCompleted = new Blockchain.PersistCompleted - { - Block = new Block - { - Version = header.Version, - PrevHash = header.PrevHash, - MerkleRoot = header.MerkleRoot, - Timestamp = header.Timestamp, - Index = header.Index, - NextConsensus = header.NextConsensus, - Transactions = new Transaction[0] - } - }; - Console.WriteLine("\n=========================="); - Console.WriteLine("Telling a new block to actor consensus..."); - Console.WriteLine("will trigger OnPersistCompleted without OnStart flag!"); - // OnPersist will not launch timer, we need OnStart - actorConsensus.Tell(testPersistCompleted); - Console.WriteLine("\n=========================="); - - Console.WriteLine("\n=========================="); - Console.WriteLine("will start consensus!"); - actorConsensus.Tell(new ConsensusService.Start - { - IgnoreRecoveryLogs = true - }); - - Console.WriteLine("Waiting for subscriber recovery message..."); - // The next line force a waits, then, subscriber keeps running its thread - // In the next case it waits for a Msg of type LocalNode.SendDirectly - // 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) - { - mockContext.Object.LastSeenMessage[validator] = 0; - } - // Ensuring cast of type ConsensusPayload from the received message from subscriber - ExtensiblePayload initialRecoveryPayload = (ExtensiblePayload)askingForInitialRecovery.Inventory; - // Ensuring casting of type RecoveryRequest - RecoveryRequest rrm = initialRecoveryPayload.Data.AsSerializable(); - rrm.Timestamp.Should().Be(defaultTimestamp); - - Console.WriteLine("Waiting for backup ChangeView... "); - var backupOnAskingChangeView = subscriber.ExpectMsg(); - var changeViewPayload = (ExtensiblePayload)backupOnAskingChangeView.Inventory; - ChangeView cvm = changeViewPayload.Data.AsSerializable(); - cvm.Timestamp.Should().Be(defaultTimestamp); - cvm.ViewNumber.Should().Be(0); - cvm.Reason.Should().Be(ChangeViewReason.Timeout); - - // Original Contract - Contract originalContract = Contract.CreateMultiSigContract(mockContext.Object.M, mockContext.Object.Validators); - Console.WriteLine($"\nORIGINAL Contract is: {originalContract.ScriptHash}"); - Console.WriteLine($"ORIGINAL NextConsensus: {mockContext.Object.Block.NextConsensus}\nENSURING values..."); - originalContract.ScriptHash.Should().Be(UInt160.Parse("0xe239c7228fa6b46cc0cf43623b2f934301d0b4f7")); - mockContext.Object.Block.NextConsensus.Should().Be(UInt160.Parse("0xe239c7228fa6b46cc0cf43623b2f934301d0b4f7")); - - Console.WriteLine("\n=========================="); - Console.WriteLine("will trigger OnPersistCompleted again with OnStart flag!"); - actorConsensus.Tell(testPersistCompleted); - Console.WriteLine("\n=========================="); - - // Disabling flag ViewChanging by reverting cache of changeview that was sent - mockContext.Object.ChangeViewPayloads[mockContext.Object.MyIndex] = null; - Console.WriteLine("Forcing Failed nodes for recovery request... "); - mockContext.Object.CountFailed.Should().Be(0); - mockContext.Object.LastSeenMessage.Clear(); - mockContext.Object.CountFailed.Should().Be(7); - Console.WriteLine("\nWaiting for recovery due to failed nodes... "); - var backupOnRecoveryDueToFailedNodes = subscriber.ExpectMsg(); - var recoveryPayload = (ExtensiblePayload)backupOnRecoveryDueToFailedNodes.Inventory; - rrm = recoveryPayload.Data.AsSerializable(); - rrm.Timestamp.Should().Be(defaultTimestamp); - - Console.WriteLine("will create template MakePrepareRequest..."); - mockContext.Object.PrevHeader.Timestamp = defaultTimestamp; - mockContext.Object.PrevHeader.NextConsensus.Should().Be(UInt160.Parse("0xe239c7228fa6b46cc0cf43623b2f934301d0b4f7")); - var prepReq = mockContext.Object.MakePrepareRequest(); - var ppToSend = prepReq.Data.AsSerializable(); - // Forcing hashes to 0 because mempool is currently shared - ppToSend.TransactionHashes = new UInt256[0]; - ppToSend.TransactionHashes.Length.Should().Be(0); - Console.WriteLine($"\nAsserting PreparationPayloads is 1 (After MakePrepareRequest)..."); - mockContext.Object.PreparationPayloads.Count(p => p != null).Should().Be(1); - mockContext.Object.PreparationPayloads[ppToSend.ValidatorIndex] = null; - - Console.WriteLine("will tell prepare request!"); - prepReq = new ExtensiblePayload - { - Category = "Consensus", - ValidBlockStart = 0, - ValidBlockEnd = ppToSend.BlockIndex, - Sender = prepReq.Sender, - Data = ppToSend.ToArray(), - Witness = prepReq.Witness - }; - TellConsensusPayload(actorConsensus, prepReq); - Console.WriteLine("Waiting for something related to the PrepRequest...\nNothing happens...Recovery will come due to failed nodes"); - var backupOnRecoveryDueToFailedNodesII = subscriber.ExpectMsg(); - var recoveryPayloadII = (ExtensiblePayload)backupOnRecoveryDueToFailedNodesII.Inventory; - rrm = recoveryPayloadII.Data.AsSerializable(); - Console.WriteLine($"\nAsserting PreparationPayloads is 0..."); - mockContext.Object.PreparationPayloads.Count(p => p != null).Should().Be(0); - Console.WriteLine($"\nAsserting CountFailed is 6..."); - mockContext.Object.CountFailed.Should().Be(6); - - Console.WriteLine("\nFailed because it is not primary and it created the prereq...Time to adjust"); - ppToSend.ValidatorIndex = 1; //simulating primary as prepreq creator (signature is skip, no problem) - // cleaning old try with Self ValidatorIndex - mockContext.Object.PreparationPayloads[mockContext.Object.MyIndex] = null; - - prepReq = new ExtensiblePayload - { - Category = "Consensus", - ValidBlockStart = 0, - ValidBlockEnd = ppToSend.BlockIndex, - Sender = mockContext.Object.GetSender(ppToSend.ValidatorIndex), - Data = ppToSend.ToArray(), - Witness = prepReq.Witness - }; - TellConsensusPayload(actorConsensus, prepReq); - var OnPrepResponse = subscriber.ExpectMsg(); - var prepResponsePayload = (ExtensiblePayload)OnPrepResponse.Inventory; - PrepareResponse prm = prepResponsePayload.Data.AsSerializable(); - prm.PreparationHash.Should().Be(prepReq.Hash); - Console.WriteLine("\nAsserting PreparationPayloads count is 2..."); - mockContext.Object.PreparationPayloads.Count(p => p != null).Should().Be(2); - Console.WriteLine($"\nAsserting CountFailed is 5..."); - mockContext.Object.CountFailed.Should().Be(5); - - // Simulating CN 3 - TellConsensusPayload(actorConsensus, GetPayloadAndModifyValidator(mockContext.Object, prepResponsePayload, 2)); - //Waiting for RecoveryRequest for a more deterministic UT - backupOnRecoveryDueToFailedNodes = subscriber.ExpectMsg(); - recoveryPayload = (ExtensiblePayload)backupOnRecoveryDueToFailedNodes.Inventory; - rrm = recoveryPayload.Data.AsSerializable(); - rrm.Timestamp.Should().Be(defaultTimestamp); - //Asserts - Console.WriteLine("\nAsserting PreparationPayloads count is 3..."); - mockContext.Object.PreparationPayloads.Count(p => p != null).Should().Be(3); - Console.WriteLine($"\nAsserting CountFailed is 4..."); - mockContext.Object.CountFailed.Should().Be(4); - - // Simulating CN 5 - TellConsensusPayload(actorConsensus, GetPayloadAndModifyValidator(mockContext.Object, prepResponsePayload, 4)); - //Waiting for RecoveryRequest for a more deterministic UT - backupOnRecoveryDueToFailedNodes = subscriber.ExpectMsg(); - recoveryPayload = (ExtensiblePayload)backupOnRecoveryDueToFailedNodes.Inventory; - rrm = recoveryPayload.Data.AsSerializable(); - rrm.Timestamp.Should().Be(defaultTimestamp); - //Asserts - Console.WriteLine("\nAsserting PreparationPayloads count is 4..."); - mockContext.Object.PreparationPayloads.Count(p => p != null).Should().Be(4); - Console.WriteLine($"\nAsserting CountFailed is 3..."); - mockContext.Object.CountFailed.Should().Be(3); - - // Simulating CN 4 - TellConsensusPayload(actorConsensus, GetPayloadAndModifyValidator(mockContext.Object, prepResponsePayload, 3)); - var onCommitPayload = subscriber.ExpectMsg(); - var commitPayload = (ExtensiblePayload)onCommitPayload.Inventory; - Commit cm = commitPayload.Data.AsSerializable(); - Console.WriteLine("\nAsserting PreparationPayloads count is 5..."); - mockContext.Object.PreparationPayloads.Count(p => p != null).Should().Be(5); - Console.WriteLine("\nAsserting CountCommitted is 1..."); - mockContext.Object.CountCommitted.Should().Be(1); - Console.WriteLine($"\nAsserting CountFailed is 2..."); - mockContext.Object.CountFailed.Should().Be(2); - - Console.WriteLine($"ORIGINAL BlockHash: {mockContext.Object.Block.Hash}"); - Console.WriteLine($"ORIGINAL Block NextConsensus: {mockContext.Object.Block.NextConsensus}"); - - for (int i = 0; i < mockContext.Object.Validators.Length; i++) - Console.WriteLine($"{mockContext.Object.Validators[i]}/{Contract.CreateSignatureContract(mockContext.Object.Validators[i]).ScriptHash}"); - mockContext.Object.Validators = new ECPoint[7] - { - kp_array[0].PublicKey, - kp_array[1].PublicKey, - kp_array[2].PublicKey, - kp_array[3].PublicKey, - kp_array[4].PublicKey, - kp_array[5].PublicKey, - kp_array[6].PublicKey - }; - Console.WriteLine($"Generated keypairs PKey:"); - //refresh LastSeenMessage - mockContext.Object.LastSeenMessage.Clear(); - 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); - Console.WriteLine($"\nContract updated: {updatedContract.ScriptHash}"); - - // =============================================================== - CachedCommittee cachedCommittee = new CachedCommittee(mockContext.Object.Validators.Select(p => (p, BigInteger.Zero))); - mockContext.Object.Snapshot.Storages.Delete(CreateStorageKeyForNativeNeo(14)); - mockContext.Object.Snapshot.Storages.Add(CreateStorageKeyForNativeNeo(14), new StorageItem() - { - Value = BinarySerializer.Serialize(cachedCommittee.ToStackItem(null), 4096) - }); - mockContext.Object.Snapshot.Commit(); - // =============================================================== - - // Forcing next consensus - var originalBlockHashData = mockContext.Object.Block.GetHashData(); - mockContext.Object.Block.NextConsensus = updatedContract.ScriptHash; - mockContext.Object.Block.Header.NextConsensus = updatedContract.ScriptHash; - var originalBlockMerkleRoot = mockContext.Object.Block.MerkleRoot; - Console.WriteLine($"\noriginalBlockMerkleRoot: {originalBlockMerkleRoot}"); - var updatedBlockHashData = mockContext.Object.Block.GetHashData(); - Console.WriteLine($"originalBlockHashData: {originalBlockHashData.ToScriptHash()}"); - Console.WriteLine($"updatedBlockHashData: {updatedBlockHashData.ToScriptHash()}"); - - Console.WriteLine("\n\n=========================="); - Console.WriteLine("\nBasic commits Signatures verification"); - // Basic tests for understanding signatures and ensuring signatures of commits are correct on tests - - - var cmPayloadTemp = GetCommitPayloadModifiedAndSignedCopy(mockContext.Object, commitPayload, 6, kp_array[6], updatedBlockHashData); - Crypto.VerifySignature(originalBlockHashData, cm.Signature, mockContext.Object.Validators[0]).Should().BeFalse(); - Crypto.VerifySignature(updatedBlockHashData, cm.Signature, mockContext.Object.Validators[0]).Should().BeFalse(); - Crypto.VerifySignature(originalBlockHashData, cmPayloadTemp.Data.AsSerializable().Signature, mockContext.Object.Validators[6]).Should().BeFalse(); - Crypto.VerifySignature(updatedBlockHashData, cmPayloadTemp.Data.AsSerializable().Signature, mockContext.Object.Validators[6]).Should().BeTrue(); - Console.WriteLine("\n=========================="); - - Console.WriteLine("\n=========================="); - Console.WriteLine("\nCN7 simulation time"); - TellConsensusPayload(actorConsensus, cmPayloadTemp); - var tempPayloadToBlockAndWait = subscriber.ExpectMsg(); - var rmPayload = (ExtensiblePayload)tempPayloadToBlockAndWait.Inventory; - RecoveryMessage rmm = rmPayload.Data.AsSerializable(); - Console.WriteLine("\nAsserting CountCommitted is 2..."); - mockContext.Object.CountCommitted.Should().Be(2); - Console.WriteLine($"\nAsserting CountFailed is 1..."); - mockContext.Object.CountFailed.Should().Be(6); - - Console.WriteLine("\nCN6 simulation time"); - TellConsensusPayload(actorConsensus, GetCommitPayloadModifiedAndSignedCopy(mockContext.Object, commitPayload, 5, kp_array[5], updatedBlockHashData)); - tempPayloadToBlockAndWait = subscriber.ExpectMsg(); - rmPayload = (ExtensiblePayload)tempPayloadToBlockAndWait.Inventory; - rmm = rmPayload.Data.AsSerializable(); - Console.WriteLine("\nAsserting CountCommitted is 3..."); - mockContext.Object.CountCommitted.Should().Be(3); - Console.WriteLine($"\nAsserting CountFailed is 0..."); - mockContext.Object.CountFailed.Should().Be(5); - - Console.WriteLine("\nCN5 simulation time"); - TellConsensusPayload(actorConsensus, GetCommitPayloadModifiedAndSignedCopy(mockContext.Object, commitPayload, 4, kp_array[4], updatedBlockHashData)); - tempPayloadToBlockAndWait = subscriber.ExpectMsg(); - Console.WriteLine("\nAsserting CountCommitted is 4..."); - mockContext.Object.CountCommitted.Should().Be(4); - - // ============================================= - // Testing commit with wrong signature not valid - // It will be invalid signature because we did not change ECPoint - Console.WriteLine("\nCN4 simulation time. Wrong signature, KeyPair is not known"); - TellConsensusPayload(actorConsensus, GetPayloadAndModifyValidator(mockContext.Object, commitPayload, 3)); - Console.WriteLine("\nWaiting for recovery due to failed nodes... "); - var backupOnRecoveryMessageAfterCommit = subscriber.ExpectMsg(); - rmPayload = (ExtensiblePayload)backupOnRecoveryMessageAfterCommit.Inventory; - rmm = rmPayload.Data.AsSerializable(); - Console.WriteLine("\nAsserting CountCommitted is 4 (Again)..."); - mockContext.Object.CountCommitted.Should().Be(4); - Console.WriteLine("\nAsserting recovery message Preparation is 5..."); - rmm.PreparationMessages.Count().Should().Be(5); - Console.WriteLine("\nAsserting recovery message CommitMessages is 4..."); - rmm.CommitMessages.Count().Should().Be(4); - // ============================================= - - Console.WriteLine($"\nForcing block {mockContext.Object.Block.GetHashData().ToScriptHash()} PrevHash to UInt256.Zero"); - // Another option would be to manipulate Blockchain.Singleton.GetSnapshot().Blocks.GetAndChange - // We would need to get the PrevHash and change the NextConsensus field - var oldPrevHash = mockContext.Object.Block.PrevHash; - mockContext.Object.Block.PrevHash = UInt256.Zero; - //Payload should also be forced, otherwise OnConsensus will not pass - Console.WriteLine($"\nNew Hash is {mockContext.Object.Block.GetHashData().ToScriptHash()}"); - Console.WriteLine($"\nForcing block VerificationScript to {updatedContract.Script.ToScriptHash()}"); - // The default behavior for BlockBase, when PrevHash = UInt256.Zero, is to use its own Witness - mockContext.Object.Block.Witness = new Witness { }; - mockContext.Object.Block.Witness.VerificationScript = updatedContract.Script; - Console.WriteLine($"\nUpdating BlockBase Witness scripthash to: {mockContext.Object.Block.Witness.ScriptHash}"); - Console.WriteLine($"\nNew Hash is {mockContext.Object.Block.GetHashData().ToScriptHash()}"); - - Console.WriteLine("\nCN4 simulation time - Final needed signatures"); - TellConsensusPayload(actorConsensus, GetCommitPayloadModifiedAndSignedCopy(mockContext.Object, commitPayload, 3, kp_array[3], mockContext.Object.Block.GetHashData())); - - Console.WriteLine("\nWait for subscriber Block"); - var utBlock = subscriber.ExpectMsg(); - Console.WriteLine("\nAsserting CountCommitted is 5..."); - mockContext.Object.CountCommitted.Should().Be(5); - - Console.WriteLine($"\nAsserting block NextConsensus..{utBlock.NextConsensus}"); - utBlock.NextConsensus.Should().Be(updatedContract.ScriptHash); - Console.WriteLine("\n=========================="); - - // ============================================= - Console.WriteLine("\nRecovery simulation..."); - mockContext.Object.CommitPayloads = new ExtensiblePayload[mockContext.Object.Validators.Length]; - // avoiding the BlockSent flag - mockContext.Object.Block.Transactions = null; - // ensuring same hash as snapshot - mockContext.Object.Block.PrevHash = oldPrevHash; - - Console.WriteLine("\nAsserting CountCommitted is 0..."); - mockContext.Object.CountCommitted.Should().Be(0); - Console.WriteLine($"\nAsserting CountFailed is 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.Clear(); - mockContext.Object.CountFailed.Should().Be(7); - - TellConsensusPayload(actorConsensus, rmPayload); - - Console.WriteLine("\nWaiting for RecoveryRequest before final asserts..."); - var onRecoveryRequestAfterRecovery = subscriber.ExpectMsg(); - var rrPayload = (ExtensiblePayload)onRecoveryRequestAfterRecovery.Inventory; - var rrMessage = rrPayload.Data.AsSerializable(); - - // It should be 3 because the commit generated by the default wallet is still invalid - Console.WriteLine("\nAsserting CountCommitted is 3 (after recovery)..."); - mockContext.Object.CountCommitted.Should().Be(3); - // ============================================= - - // ============================================= - // ============================================================================ - // finalize ConsensusService actor - // ============================================================================ - Console.WriteLine("Returning states."); - // Updating context.Snapshot with the one that was committed - Console.WriteLine("mockContext Reset for returning Blockchain.Singleton snapshot to original state."); - mockContext.Object.Reset(0); - mockContext.Object.Snapshot.Storages.Delete(CreateStorageKeyForNativeNeo(14)); - cachedCommittee = new CachedCommittee(Blockchain.StandbyCommittee.Select(p => (p, BigInteger.Zero))); - mockContext.Object.Snapshot.Storages.Add(CreateStorageKeyForNativeNeo(14), new StorageItem - { - Value = BinarySerializer.Serialize(cachedCommittee.ToStackItem(null), 4096) - }); - mockContext.Object.Snapshot.Commit(); - - Console.WriteLine("mockContext Reset."); - mockContext.Object.Reset(0); - Console.WriteLine("TimeProvider Reset."); - TimeProvider.ResetToDefault(); - - Console.WriteLine("Finalizing consensus service actor."); - Sys.Stop(actorConsensus); - Console.WriteLine("Actor actorConsensus Stopped.\n"); - } - - /// - /// Get a clone of a ConsensusPayload that contains a Commit Message, change its currentValidatorIndex and sign it - /// - /// ConsensusPayload that will be modified - /// new ValidatorIndex for the cpToCopy - /// KeyPair that will be used for signing the Commit message used for creating blocks - /// HashCode of the Block that is being produced and current being signed - public ExtensiblePayload GetCommitPayloadModifiedAndSignedCopy(ConsensusContext context, ExtensiblePayload cpToCopy, byte vI, KeyPair kp, byte[] blockHashToSign) - { - var cpCommitTemp = cpToCopy.ToArray().AsSerializable(); - cpCommitTemp.Sender = context.GetSender(vI); - var oldMessage = cpCommitTemp.Data.AsSerializable(); - cpCommitTemp.Data = new Commit - { - BlockIndex = oldMessage.BlockIndex, - ValidatorIndex = vI, - ViewNumber = oldMessage.ViewNumber, - Signature = Crypto.Sign(blockHashToSign, kp.PrivateKey, kp.PublicKey.EncodePoint(false).Skip(1).ToArray()) - }.ToArray(); - SignPayload(cpCommitTemp, kp); - return cpCommitTemp; - } - - /// - /// Get a clone of a ConsensusPayload and change its currentValidatorIndex - /// - /// ConsensusPayload that will be modified - /// new ValidatorIndex for the cpToCopy - public ExtensiblePayload GetPayloadAndModifyValidator(ConsensusContext context, ExtensiblePayload cpToCopy, byte vI) - { - var cpTemp = cpToCopy.ToArray().AsSerializable(); - var message = ConsensusMessage.DeserializeFrom(cpTemp.Data); - message.ValidatorIndex = vI; - cpTemp.Data = message.ToArray(); - cpTemp.Sender = context.GetSender(vI); - return cpTemp; - } - - private void SignPayload(ExtensiblePayload payload, KeyPair kp) - { - ContractParametersContext sc; - try - { - sc = new ContractParametersContext(payload); - byte[] signature = sc.Verifiable.Sign(kp); - sc.AddSignature(Contract.CreateSignatureContract(kp.PublicKey), kp.PublicKey, signature); - } - catch (InvalidOperationException) - { - return; - } - payload.Witness = sc.GetWitnesses()[0]; - } - - [TestMethod] - public void TestSerializeAndDeserializeConsensusContext() - { - var consensusContext = new ConsensusContext(null, null) - { - Block = new Block - { - PrevHash = Blockchain.GenesisBlock.Hash, - Index = 1, - Timestamp = 4244941711, - NextConsensus = UInt160.Parse("5555AAAA5555AAAA5555AAAA5555AAAA5555AAAA"), - ConsensusData = new ConsensusData - { - PrimaryIndex = 6 - } - }, - ViewNumber = 2, - Validators = new ECPoint[7] - { - ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", Neo.Cryptography.ECC.ECCurve.Secp256r1), - ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", Neo.Cryptography.ECC.ECCurve.Secp256r1) - }, - MyIndex = -1 - }; - var testTx1 = TestUtils.CreateRandomHashTransaction(); - var testTx2 = TestUtils.CreateRandomHashTransaction(); - - int txCountToInlcude = 256; - consensusContext.TransactionHashes = new UInt256[txCountToInlcude]; - - Transaction[] txs = new Transaction[txCountToInlcude]; - for (int i = 0; i < txCountToInlcude; i++) - { - txs[i] = TestUtils.CreateRandomHashTransaction(); - consensusContext.TransactionHashes[i] = txs[i].Hash; - } - // consensusContext.TransactionHashes = new UInt256[2] {testTx1.Hash, testTx2.Hash}; - consensusContext.Transactions = txs.ToDictionary(p => p.Hash); - - consensusContext.PreparationPayloads = new ExtensiblePayload[consensusContext.Validators.Length]; - var prepareRequestMessage = new PrepareRequest - { - PrevHash = consensusContext.Block.PrevHash, - TransactionHashes = consensusContext.TransactionHashes, - Timestamp = 23 - }; - consensusContext.PreparationPayloads[6] = MakeSignedPayload(consensusContext, prepareRequestMessage, 6, new[] { (byte)'3', (byte)'!' }); - consensusContext.PreparationPayloads[0] = MakeSignedPayload(consensusContext, new PrepareResponse { PreparationHash = consensusContext.PreparationPayloads[6].Hash }, 0, new[] { (byte)'t', (byte)'e' }); - consensusContext.PreparationPayloads[1] = MakeSignedPayload(consensusContext, new PrepareResponse { PreparationHash = consensusContext.PreparationPayloads[6].Hash }, 1, new[] { (byte)'s', (byte)'t' }); - consensusContext.PreparationPayloads[2] = null; - consensusContext.PreparationPayloads[3] = MakeSignedPayload(consensusContext, new PrepareResponse { PreparationHash = consensusContext.PreparationPayloads[6].Hash }, 3, new[] { (byte)'1', (byte)'2' }); - consensusContext.PreparationPayloads[4] = null; - consensusContext.PreparationPayloads[5] = null; - - consensusContext.CommitPayloads = new ExtensiblePayload[consensusContext.Validators.Length]; - using (SHA256 sha256 = SHA256.Create()) - { - consensusContext.CommitPayloads[3] = MakeSignedPayload(consensusContext, new Commit { Signature = sha256.ComputeHash(testTx1.Hash.ToArray()).Concat(sha256.ComputeHash(testTx1.Hash.ToArray())).ToArray() }, 3, new[] { (byte)'3', (byte)'4' }); - consensusContext.CommitPayloads[6] = MakeSignedPayload(consensusContext, new Commit { Signature = sha256.ComputeHash(testTx2.Hash.ToArray()).Concat(sha256.ComputeHash(testTx2.Hash.ToArray())).ToArray() }, 3, new[] { (byte)'6', (byte)'7' }); - } - - consensusContext.Block.Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS(); - - consensusContext.ChangeViewPayloads = new ExtensiblePayload[consensusContext.Validators.Length]; - consensusContext.ChangeViewPayloads[0] = MakeSignedPayload(consensusContext, new ChangeView { ViewNumber = 1, Timestamp = 6 }, 0, new[] { (byte)'A' }); - consensusContext.ChangeViewPayloads[1] = MakeSignedPayload(consensusContext, new ChangeView { ViewNumber = 1, Timestamp = 5 }, 1, new[] { (byte)'B' }); - consensusContext.ChangeViewPayloads[2] = null; - consensusContext.ChangeViewPayloads[3] = MakeSignedPayload(consensusContext, new ChangeView { ViewNumber = 1, Timestamp = uint.MaxValue }, 3, new[] { (byte)'C' }); - consensusContext.ChangeViewPayloads[4] = null; - consensusContext.ChangeViewPayloads[5] = null; - consensusContext.ChangeViewPayloads[6] = MakeSignedPayload(consensusContext, new ChangeView { ViewNumber = 1, Timestamp = 1 }, 6, new[] { (byte)'D' }); - - consensusContext.LastChangeViewPayloads = new ExtensiblePayload[consensusContext.Validators.Length]; - - var copiedContext = TestUtils.CopyMsgBySerialization(consensusContext, new ConsensusContext(null, null)); - - copiedContext.Block.PrevHash.Should().Be(consensusContext.Block.PrevHash); - copiedContext.Block.Index.Should().Be(consensusContext.Block.Index); - copiedContext.ViewNumber.Should().Be(consensusContext.ViewNumber); - copiedContext.Validators.Should().BeEquivalentTo(consensusContext.Validators); - copiedContext.MyIndex.Should().Be(consensusContext.MyIndex); - copiedContext.Block.ConsensusData.PrimaryIndex.Should().Be(consensusContext.Block.ConsensusData.PrimaryIndex); - copiedContext.Block.Timestamp.Should().Be(consensusContext.Block.Timestamp); - copiedContext.Block.NextConsensus.Should().Be(consensusContext.Block.NextConsensus); - copiedContext.TransactionHashes.Should().BeEquivalentTo(consensusContext.TransactionHashes); - copiedContext.Transactions.Should().BeEquivalentTo(consensusContext.Transactions); - copiedContext.Transactions.Values.Should().BeEquivalentTo(consensusContext.Transactions.Values); - copiedContext.PreparationPayloads.Should().BeEquivalentTo(consensusContext.PreparationPayloads); - copiedContext.CommitPayloads.Should().BeEquivalentTo(consensusContext.CommitPayloads); - copiedContext.ChangeViewPayloads.Should().BeEquivalentTo(consensusContext.ChangeViewPayloads); - } - - [TestMethod] - public void TestSerializeAndDeserializeRecoveryMessageWithChangeViewsAndNoPrepareRequest() - { - var msg = new RecoveryMessage - { - ChangeViewMessages = new Dictionary() - { - { - 0, - new RecoveryMessage.ChangeViewPayloadCompact - { - ValidatorIndex = 0, - OriginalViewNumber = 9, - Timestamp = 6, - InvocationScript = new[] { (byte)'A' } - } - }, - { - 1, - new RecoveryMessage.ChangeViewPayloadCompact - { - ValidatorIndex = 1, - OriginalViewNumber = 7, - Timestamp = 5, - InvocationScript = new[] { (byte)'B' } - } - }, - { - 3, - new RecoveryMessage.ChangeViewPayloadCompact - { - ValidatorIndex = 3, - OriginalViewNumber = 5, - Timestamp = 3, - InvocationScript = new[] { (byte)'C' } - } - }, - { - 6, - new RecoveryMessage.ChangeViewPayloadCompact - { - ValidatorIndex = 6, - OriginalViewNumber = 2, - Timestamp = 1, - InvocationScript = new[] { (byte)'D' } - } - } - }, - PreparationHash = new UInt256(Crypto.Hash256(new[] { (byte)'a' })), - PreparationMessages = new Dictionary() - { - { - 0, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 0, - InvocationScript = new[] { (byte)'t', (byte)'e' } - } - }, - { - 3, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 3, - InvocationScript = new[] { (byte)'1', (byte)'2' } - } - }, - { - 6, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 6, - InvocationScript = new[] { (byte)'3', (byte)'!' } - } - } - }, - CommitMessages = new Dictionary() - }; - - // msg.TransactionHashes = null; - // msg.Nonce = 0; - // msg.NextConsensus = null; - // msg.MinerTransaction = (MinerTransaction) null; - msg.PrepareRequestMessage.Should().Be(null); - - var copiedMsg = TestUtils.CopyMsgBySerialization(msg, new RecoveryMessage()); ; - - copiedMsg.ChangeViewMessages.Should().BeEquivalentTo(msg.ChangeViewMessages); - copiedMsg.PreparationHash.Should().Be(msg.PreparationHash); - copiedMsg.PreparationMessages.Should().BeEquivalentTo(msg.PreparationMessages); - copiedMsg.CommitMessages.Count.Should().Be(0); - } - - [TestMethod] - public void TestSerializeAndDeserializeRecoveryMessageWithChangeViewsAndPrepareRequest() - { - Transaction[] txs = new Transaction[5]; - for (int i = 0; i < txs.Length; i++) - txs[i] = TestUtils.CreateRandomHashTransaction(); - var msg = new RecoveryMessage - { - ChangeViewMessages = new Dictionary() - { - { - 0, - new RecoveryMessage.ChangeViewPayloadCompact - { - ValidatorIndex = 0, - OriginalViewNumber = 9, - Timestamp = 6, - InvocationScript = new[] { (byte)'A' } - } - }, - { - 1, - new RecoveryMessage.ChangeViewPayloadCompact - { - ValidatorIndex = 1, - OriginalViewNumber = 7, - Timestamp = 5, - InvocationScript = new[] { (byte)'B' } - } - }, - { - 3, - new RecoveryMessage.ChangeViewPayloadCompact - { - ValidatorIndex = 3, - OriginalViewNumber = 5, - Timestamp = 3, - InvocationScript = new[] { (byte)'C' } - } - }, - { - 6, - new RecoveryMessage.ChangeViewPayloadCompact - { - ValidatorIndex = 6, - OriginalViewNumber = 2, - Timestamp = 1, - InvocationScript = new[] { (byte)'D' } - } - } - }, - PrepareRequestMessage = new PrepareRequest - { - PrevHash = UInt256.Zero, - TransactionHashes = txs.Select(p => p.Hash).ToArray() - }, - PreparationHash = new UInt256(Crypto.Hash256(new[] { (byte)'a' })), - PreparationMessages = new Dictionary() - { - { - 0, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 0, - InvocationScript = new[] { (byte)'t', (byte)'e' } - } - }, - { - 1, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 1, - InvocationScript = new[] { (byte)'s', (byte)'t' } - } - }, - { - 3, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 3, - InvocationScript = new[] { (byte)'1', (byte)'2' } - } - } - }, - CommitMessages = new Dictionary() - }; - - var copiedMsg = TestUtils.CopyMsgBySerialization(msg, new RecoveryMessage()); ; - - copiedMsg.ChangeViewMessages.Should().BeEquivalentTo(msg.ChangeViewMessages); - copiedMsg.PrepareRequestMessage.Should().BeEquivalentTo(msg.PrepareRequestMessage); - copiedMsg.PreparationHash.Should().Be(null); - copiedMsg.PreparationMessages.Should().BeEquivalentTo(msg.PreparationMessages); - copiedMsg.CommitMessages.Count.Should().Be(0); - } - - [TestMethod] - public void TestSerializeAndDeserializeRecoveryMessageWithoutChangeViewsWithoutCommits() - { - Transaction[] txs = new Transaction[5]; - for (int i = 0; i < txs.Length; i++) - txs[i] = TestUtils.CreateRandomHashTransaction(); - var msg = new RecoveryMessage - { - ChangeViewMessages = new Dictionary(), - PrepareRequestMessage = new PrepareRequest - { - PrevHash = UInt256.Zero, - TransactionHashes = txs.Select(p => p.Hash).ToArray() - }, - PreparationMessages = new Dictionary() - { - { - 0, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 0, - InvocationScript = new[] { (byte)'t', (byte)'e' } - } - }, - { - 1, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 1, - InvocationScript = new[] { (byte)'s', (byte)'t' } - } - }, - { - 3, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 3, - InvocationScript = new[] { (byte)'1', (byte)'2' } - } - }, - { - 6, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 6, - InvocationScript = new[] { (byte)'3', (byte)'!' } - } - } - }, - CommitMessages = new Dictionary() - }; - - var copiedMsg = TestUtils.CopyMsgBySerialization(msg, new RecoveryMessage()); ; - - copiedMsg.ChangeViewMessages.Count.Should().Be(0); - copiedMsg.PrepareRequestMessage.Should().BeEquivalentTo(msg.PrepareRequestMessage); - copiedMsg.PreparationHash.Should().Be(null); - copiedMsg.PreparationMessages.Should().BeEquivalentTo(msg.PreparationMessages); - copiedMsg.CommitMessages.Count.Should().Be(0); - } - - [TestMethod] - public void TestSerializeAndDeserializeRecoveryMessageWithoutChangeViewsWithCommits() - { - Transaction[] txs = new Transaction[5]; - for (int i = 0; i < txs.Length; i++) - txs[i] = TestUtils.CreateRandomHashTransaction(); - var msg = new RecoveryMessage - { - ChangeViewMessages = new Dictionary(), - PrepareRequestMessage = new PrepareRequest - { - PrevHash = UInt256.Zero, - TransactionHashes = txs.Select(p => p.Hash).ToArray() - }, - PreparationMessages = new Dictionary() - { - { - 0, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 0, - InvocationScript = new[] { (byte)'t', (byte)'e' } - } - }, - { - 1, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 1, - InvocationScript = new[] { (byte)'s', (byte)'t' } - } - }, - { - 3, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 3, - InvocationScript = new[] { (byte)'1', (byte)'2' } - } - }, - { - 6, - new RecoveryMessage.PreparationPayloadCompact - { - ValidatorIndex = 6, - InvocationScript = new[] { (byte)'3', (byte)'!' } - } - } - }, - CommitMessages = new Dictionary - { - { - 1, - new RecoveryMessage.CommitPayloadCompact - { - ValidatorIndex = 1, - Signature = new byte[64] { (byte)'1', (byte)'2', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - InvocationScript = new[] { (byte)'1', (byte)'2' } - } - }, - { - 6, - new RecoveryMessage.CommitPayloadCompact - { - ValidatorIndex = 6, - Signature = new byte[64] { (byte)'3', (byte)'D', (byte)'R', (byte)'I', (byte)'N', (byte)'K', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - InvocationScript = new[] { (byte)'6', (byte)'7' } - } - } - } - }; - - var copiedMsg = TestUtils.CopyMsgBySerialization(msg, new RecoveryMessage()); ; - - copiedMsg.ChangeViewMessages.Count.Should().Be(0); - copiedMsg.PrepareRequestMessage.Should().BeEquivalentTo(msg.PrepareRequestMessage); - copiedMsg.PreparationHash.Should().Be(null); - copiedMsg.PreparationMessages.Should().BeEquivalentTo(msg.PreparationMessages); - copiedMsg.CommitMessages.Should().BeEquivalentTo(msg.CommitMessages); - } - - private static ExtensiblePayload MakeSignedPayload(ConsensusContext context, ConsensusMessage message, byte validatorIndex, byte[] witnessInvocationScript) - { - message.BlockIndex = context.Block.Index; - message.ValidatorIndex = validatorIndex; - return new ExtensiblePayload - { - Category = "Consensus", - ValidBlockStart = 0, - ValidBlockEnd = message.BlockIndex, - Sender = context.GetSender(validatorIndex), - Data = message.ToArray(), - Witness = new Witness - { - InvocationScript = witnessInvocationScript, - VerificationScript = Contract.CreateSignatureRedeemScript(context.Validators[validatorIndex]) - } - }; - } - - private StorageKey CreateStorageKeyForNativeNeo(byte prefix) - { - StorageKey storageKey = new StorageKey - { - Id = NativeContract.NEO.Id, - Key = new byte[sizeof(byte)] - }; - storageKey.Key[0] = prefix; - return storageKey; - } - - private void TellConsensusPayload(IActorRef actor, ExtensiblePayload payload) - { - actor.Tell(new Blockchain.RelayResult - { - Inventory = payload, - Result = VerifyResult.Succeed - }); - } - } -} diff --git a/tests/neo.UnitTests/Consensus/UT_ConsensusContext.cs b/tests/neo.UnitTests/Consensus/UT_ConsensusContext.cs deleted file mode 100644 index 297ad0e17c..0000000000 --- a/tests/neo.UnitTests/Consensus/UT_ConsensusContext.cs +++ /dev/null @@ -1,193 +0,0 @@ -using Akka.TestKit.Xunit2; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using Neo.Consensus; -using Neo.IO; -using Neo.Ledger; -using Neo.Network.P2P.Payloads; -using Neo.SmartContract.Native; -using Neo.Wallets; -using System; -using System.Linq; - -namespace Neo.UnitTests.Consensus -{ - [TestClass] - public class UT_ConsensusContext : TestKit - { - ConsensusContext _context; - KeyPair[] _validatorKeys; - - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - - var rand = new Random(); - var mockWallet = new Mock(); - mockWallet.Setup(p => p.GetAccount(It.IsAny())).Returns(p => new TestWalletAccount(p)); - - // Create dummy validators - - _validatorKeys = new KeyPair[7]; - for (int x = 0; x < _validatorKeys.Length; x++) - { - var pk = new byte[32]; - rand.NextBytes(pk); - - _validatorKeys[x] = new KeyPair(pk); - } - - _context = new ConsensusContext(mockWallet.Object, Blockchain.Singleton.Store) - { - Validators = _validatorKeys.Select(u => u.PublicKey).ToArray() - }; - _context.Reset(0); - } - - [TestCleanup] - public void Cleanup() - { - Shutdown(); - } - - [TestMethod] - public void TestMaxBlockSize_Good() - { - // Only one tx, is included - - var tx1 = CreateTransactionWithSize(200); - _context.EnsureMaxBlockLimitation(new Transaction[] { tx1 }); - EnsureContext(_context, tx1); - - // All txs included - - var max = (int)NativeContract.Policy.GetMaxTransactionsPerBlock(_context.Snapshot); - var txs = new Transaction[max]; - - for (int x = 0; x < max; x++) txs[x] = CreateTransactionWithSize(100); - - _context.EnsureMaxBlockLimitation(txs); - EnsureContext(_context, txs); - } - - [TestMethod] - public void TestMaxBlockSize_Exceed() - { - // Two tx, the last one exceed the size rule, only the first will be included - - var tx1 = CreateTransactionWithSize(200); - var tx2 = CreateTransactionWithSize(256 * 1024); - _context.EnsureMaxBlockLimitation(new Transaction[] { tx1, tx2 }); - EnsureContext(_context, tx1); - - // Exceed txs number, just MaxTransactionsPerBlock included - - var max = (int)NativeContract.Policy.GetMaxTransactionsPerBlock(_context.Snapshot); - var txs = new Transaction[max + 1]; - - for (int x = 0; x < max; x++) txs[x] = CreateTransactionWithSize(100); - - _context.EnsureMaxBlockLimitation(txs); - EnsureContext(_context, txs.Take(max).ToArray()); - } - - [TestMethod] - public void TestMaxBlockSytemFee() - { - var tx1 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2); - - // Less than MaxBlockSystemFee - _context.EnsureMaxBlockLimitation(new Transaction[] { tx1 }); - EnsureContext(_context, new Transaction[] { tx1 }); - - // Equal MaxBlockSystemFee - tx1 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2 + 1); - var tx2 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2 - 1); - - _context.EnsureMaxBlockLimitation(new Transaction[] { tx1, tx2 }); - EnsureContext(_context, new Transaction[] { tx1, tx2 }); - - // Exceed MaxBlockSystemFee - tx1 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2 + 3); - tx2 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2 - 3); - var tx3 = CreateTransactionWithSytemFee(NativeContract.Policy.GetMaxBlockSystemFee(_context.Snapshot) / 2 - 4); - - _context.EnsureMaxBlockLimitation(new Transaction[] { tx1, tx2, tx3 }); - EnsureContext(_context, new Transaction[] { tx1, tx2 }); - } - - private Transaction CreateTransactionWithSize(int v) - { - var r = new Random(); - var tx = new Transaction() - { - Attributes = System.Array.Empty(), - Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, - NetworkFee = 0, - Nonce = (uint)Environment.TickCount, - Script = new byte[0], - SystemFee = 0, - ValidUntilBlock = (uint)r.Next(), - Version = 0, - Witnesses = new Witness[0], - }; - - // Could be higher (few bytes) if varSize grows - tx.Script = new byte[v - tx.Size]; - return tx; - } - - private Transaction CreateTransactionWithSytemFee(long fee) - { - var tx = new Transaction() - { - Attributes = System.Array.Empty(), - Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, - NetworkFee = 0, - Nonce = (uint)Environment.TickCount, - Script = new byte[0], - SystemFee = fee, - ValidUntilBlock = int.MaxValue, - Version = 0, - Witnesses = new Witness[0], - }; - return tx; - } - - private Block SignBlock(ConsensusContext context) - { - context.Block.MerkleRoot = null; - - // Fake commits - - for (int x = 0; x < _validatorKeys.Length; x++) - { - _context.MyIndex = x; - - var com = _context.MakeCommit(); - _context.CommitPayloads[_context.MyIndex] = com; - } - - return context.CreateBlock(); - } - - private void EnsureContext(ConsensusContext context, params Transaction[] expected) - { - // Check all tx - - Assert.AreEqual(expected.Length, context.Transactions.Count); - Assert.IsTrue(expected.All(tx => context.Transactions.ContainsKey(tx.Hash))); - - Assert.AreEqual(expected.Length, context.TransactionHashes.Length); - Assert.IsTrue(expected.All(tx => context.TransactionHashes.Count(t => t == tx.Hash) == 1)); - - // Ensure length - - var block = SignBlock(context); - - Assert.AreEqual(context.GetExpectedBlockSize(), block.Size); - Assert.IsTrue(block.Size < NativeContract.Policy.GetMaxBlockSize(context.Snapshot)); - } - } -} diff --git a/tests/neo.UnitTests/Consensus/UT_ConsensusServiceMailbox.cs b/tests/neo.UnitTests/Consensus/UT_ConsensusServiceMailbox.cs deleted file mode 100644 index 9de9617e65..0000000000 --- a/tests/neo.UnitTests/Consensus/UT_ConsensusServiceMailbox.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Akka.TestKit.Xunit2; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Consensus; -using Neo.Ledger; -using Neo.Network.P2P.Payloads; -using System; - -namespace Neo.UnitTests.Consensus -{ - [TestClass] - public class UT_ConsensusServiceMailbox : TestKit - { - private static readonly Random TestRandom = new Random(1337); // use fixed seed for guaranteed determinism - - ConsensusServiceMailbox uut; - - [TestCleanup] - public void Cleanup() - { - Shutdown(); - } - - [TestInitialize] - public void TestSetup() - { - Akka.Actor.ActorSystem system = Sys; - var config = TestKit.DefaultConfig; - var akkaSettings = new Akka.Actor.Settings(system, config); - uut = new ConsensusServiceMailbox(akkaSettings, config); - } - - [TestMethod] - public void ConsensusServiceMailbox_Test_IsHighPriority() - { - // high priority - uut.IsHighPriority(new ExtensiblePayload()).Should().Be(true); - uut.IsHighPriority(new ConsensusService.SetViewNumber()).Should().Be(true); - uut.IsHighPriority(new ConsensusService.Timer()).Should().Be(true); - uut.IsHighPriority(new Blockchain.PersistCompleted()).Should().Be(true); - - // any random object should not have priority - object obj = null; - uut.IsHighPriority(obj).Should().Be(false); - } - } -} diff --git a/tests/neo.UnitTests/Consensus/UT_RecoveryRequest.cs b/tests/neo.UnitTests/Consensus/UT_RecoveryRequest.cs deleted file mode 100644 index 3c66f59533..0000000000 --- a/tests/neo.UnitTests/Consensus/UT_RecoveryRequest.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Consensus; -using Neo.IO; - -namespace Neo.UnitTests.Consensus -{ - [TestClass] - public class UT_RecoveryRequest - { - [TestMethod] - public void Size_Get() - { - var test = new RecoveryRequest() { Timestamp = 1, ViewNumber = 1 }; - test.Size.Should().Be(15); - } - - [TestMethod] - public void DeserializeAndSerialize() - { - var test = new RecoveryRequest() { ViewNumber = 1, Timestamp = 123 }; - var clone = test.ToArray().AsSerializable(); - - Assert.AreEqual(test.Timestamp, clone.Timestamp); - Assert.AreEqual(test.Type, clone.Type); - Assert.AreEqual(test.ViewNumber, clone.ViewNumber); - } - } -} diff --git a/tests/neo.UnitTests/UT_NeoSystem.cs b/tests/neo.UnitTests/UT_NeoSystem.cs index 5e440d8374..646d214f6c 100644 --- a/tests/neo.UnitTests/UT_NeoSystem.cs +++ b/tests/neo.UnitTests/UT_NeoSystem.cs @@ -22,8 +22,5 @@ public void Setup() [TestMethod] public void TestGetTaskManager() => neoSystem.TaskManager.Should().NotBeNull(); - - [TestMethod] - public void TestGetConsensus() => neoSystem.Consensus.Should().BeNull(); } } From 5ce3f4c2d758d914e4eb16c43b37dddd95b06539 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Sun, 10 Jan 2021 21:19:27 +0800 Subject: [PATCH 2/3] Update IP2PPlugin.cs --- src/neo/Plugins/IP2PPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neo/Plugins/IP2PPlugin.cs b/src/neo/Plugins/IP2PPlugin.cs index 8323339d75..ea792e721c 100644 --- a/src/neo/Plugins/IP2PPlugin.cs +++ b/src/neo/Plugins/IP2PPlugin.cs @@ -6,6 +6,6 @@ namespace Neo.Plugins public interface IP2PPlugin { bool OnP2PMessage(Message message) => true; - void OnVerifiedInventory(IInventory inventory); + void OnVerifiedInventory(IInventory inventory) { } } } From f100218032d40505ef403d916a744fbc4e60f8b6 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Sun, 10 Jan 2021 21:33:20 +0800 Subject: [PATCH 3/3] Update IConsensusProvider.cs --- src/neo/Plugins/IConsensusProvider.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/neo/Plugins/IConsensusProvider.cs b/src/neo/Plugins/IConsensusProvider.cs index f9885da754..a7e021863a 100644 --- a/src/neo/Plugins/IConsensusProvider.cs +++ b/src/neo/Plugins/IConsensusProvider.cs @@ -1,7 +1,9 @@ +using Neo.Wallets; + namespace Neo.Plugins { public interface IConsensusProvider { - void Start(); + void Start(Wallet wallet); } }