private static ConsensusPayload MakeSignedPayload(ConsensusContext context, ConsensusMessage message, ushort validatorIndex, byte[] witnessInvocationScript) { return(new ConsensusPayload { Version = context.Block.Version, PrevHash = context.Block.PrevHash, BlockIndex = context.Block.Index, ValidatorIndex = validatorIndex, ConsensusMessage = message, Witness = new Witness { InvocationScript = witnessInvocationScript, VerificationScript = Contract.CreateSignatureRedeemScript(context.Validators[validatorIndex]) } }); }
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)); }
Block RunConsensus() { if (chain.ConsensusNodes.Count == 1) { var ctx = new ConsensusContext(nodeWallet, Blockchain.Singleton.Store); ctx.Reset(0); ctx.MakePrepareRequest(); ctx.MakeCommit(); return(ctx.CreateBlock()); } // create ConsensusContext for each ConsensusNode var contexts = new ConsensusContext[chain.ConsensusNodes.Count]; for (int x = 0; x < contexts.Length; x++) { contexts[x] = new ConsensusContext(DevWallet.FromExpressWallet(chain.ConsensusNodes[x].Wallet), Blockchain.Singleton.Store); contexts[x].Reset(0); } // find the primary node for this consensus round var primary = contexts.Single(c => c.IsPrimary); var prepareRequestPayload = primary.MakePrepareRequest(); for (int x = 0; x < contexts.Length; x++) { var context = contexts[x]; if (context.MyIndex == primary.MyIndex) { continue; } var prepareRequestMessage = context.GetMessage <PrepareRequest>(prepareRequestPayload); OnPrepareRequestReceived(context, prepareRequestPayload, prepareRequestMessage); var commitPayload = context.MakeCommit(); var commitMessage = primary.GetMessage <Commit>(commitPayload); OnCommitReceived(primary, commitPayload, commitMessage); } return(primary.CreateBlock()); }
public void ConsensusService_Primary_Sends_PrepareRequest_After_OnStart() { TestProbe subscriber = CreateTestProbe(); var mockWallet = new Mock <Wallet>(); mockWallet.Setup(p => p.GetAccount(It.IsAny <UInt160>())).Returns <UInt160>(p => new TestWalletAccount(p)); ConsensusContext context = new ConsensusContext(mockWallet.Object, TestBlockchain.GetStore()); int timeIndex = 0; var timeValues = new[] { //new DateTime(1968, 06, 01, 0, 0, 15, DateTimeKind.Utc), // For tests here new DateTime(1968, 06, 01, 0, 0, 1, DateTimeKind.Utc), // For receiving block new DateTime(1968, 06, 01, 0, 0, (int)Blockchain.SecondsPerBlock, DateTimeKind.Utc), // For Initialize new DateTime(1968, 06, 01, 0, 0, 15, DateTimeKind.Utc), // unused new DateTime(1968, 06, 01, 0, 0, 15, DateTimeKind.Utc) // unused }; //TimeProvider.Current.UtcNow.ToTimestamp().Should().Be(4244941711); //1968-06-01 00:00:15 Console.WriteLine($"time 0: {timeValues[0].ToString()} 1: {timeValues[1].ToString()} 2: {timeValues[2].ToString()} 3: {timeValues[3].ToString()}"); var timeMock = new Mock <TimeProvider>(); timeMock.SetupGet(tp => tp.UtcNow).Returns(() => timeValues[timeIndex]) .Callback(() => timeIndex++); //new DateTime(1968, 06, 01, 0, 0, 15, DateTimeKind.Utc)); TimeProvider.Current = timeMock.Object; //public void Log(string message, LogLevel level) // TODO: create ILogPlugin for Tests /* * mockConsensusContext.Setup(mr => mr.Log(It.IsAny<string>(), It.IsAny<LogLevel>())) * .Callback((string message, LogLevel level) => { * Console.WriteLine($"CONSENSUS LOG: {message}"); * } * ); */ // Creating proposed block Header header = new Header(); TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out UInt256 merkRootVal, out UInt160 val160, out uint timestampVal, out uint indexVal, out Witness scriptVal); header.Size.Should().Be(101); Console.WriteLine($"header {header} hash {header.Hash} timstamp {timestampVal}"); timestampVal.Should().Be(4244941696); //1968-06-01 00:00:00 // check basic ConsensusContext //mockConsensusContext.Object.block_received_time.ToTimestamp().Should().Be(4244941697); //1968-06-01 00:00:01 // ============================================================================ // creating ConsensusService actor // ============================================================================ TestActorRef <ConsensusService> actorConsensus = ActorOfAsTestActorRef <ConsensusService>( Akka.Actor.Props.Create(() => (ConsensusService)Activator.CreateInstance(typeof(ConsensusService), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { subscriber, subscriber, context }, null)) ); Console.WriteLine("will trigger OnPersistCompleted!"); actorConsensus.Tell(new Blockchain.PersistCompleted { Block = new Block { Version = header.Version, PrevHash = header.PrevHash, MerkleRoot = header.MerkleRoot, Timestamp = header.Timestamp, Index = header.Index, NextConsensus = header.NextConsensus } }); // OnPersist will not launch timer, we need OnStart Console.WriteLine("will start consensus!"); actorConsensus.Tell(new ConsensusService.Start { IgnoreRecoveryLogs = true }); Console.WriteLine("OnTimer should expire!"); Console.WriteLine("Waiting for subscriber message!"); // Timer should expire in one second (block_received_time at :01, initialized at :02) var answer = subscriber.ExpectMsg <LocalNode.SendDirectly>(); Console.WriteLine($"MESSAGE 1: {answer}"); //var answer2 = subscriber.ExpectMsg<LocalNode.SendDirectly>(); // expects to fail! // ============================================================================ // finalize ConsensusService actor // ============================================================================ //Thread.Sleep(4000); Sys.Stop(actorConsensus); TimeProvider.ResetToDefault(); Assert.AreEqual(1, 1); }
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", Cryptography.ECC.ECCurve.Secp256r1), ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", Cryptography.ECC.ECCurve.Secp256r1), ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", Cryptography.ECC.ECCurve.Secp256r1), ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", Cryptography.ECC.ECCurve.Secp256r1), ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", Cryptography.ECC.ECCurve.Secp256r1), ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", Cryptography.ECC.ECCurve.Secp256r1), ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", 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 ConsensusPayload[consensusContext.Validators.Length]; var prepareRequestMessage = new PrepareRequest { 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 ConsensusPayload[consensusContext.Validators.Length]; using (SHA256 sha256 = SHA256.Create()) { consensusContext.CommitPayloads[3] = MakeSignedPayload(consensusContext, new Commit { Signature = sha256.ComputeHash(testTx1.Hash.ToArray()) }, 3, new[] { (byte)'3', (byte)'4' }); consensusContext.CommitPayloads[6] = MakeSignedPayload(consensusContext, new Commit { Signature = sha256.ComputeHash(testTx2.Hash.ToArray()) }, 3, new[] { (byte)'6', (byte)'7' }); } consensusContext.Block.Timestamp = TimeProvider.Current.UtcNow.ToTimestamp(); consensusContext.ChangeViewPayloads = new ConsensusPayload[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 ConsensusPayload[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.ShouldAllBeEquivalentTo(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.ShouldAllBeEquivalentTo(consensusContext.TransactionHashes); copiedContext.Transactions.ShouldAllBeEquivalentTo(consensusContext.Transactions); copiedContext.Transactions.Values.ShouldAllBeEquivalentTo(consensusContext.Transactions.Values); copiedContext.PreparationPayloads.ShouldAllBeEquivalentTo(consensusContext.PreparationPayloads); copiedContext.CommitPayloads.ShouldAllBeEquivalentTo(consensusContext.CommitPayloads); copiedContext.ChangeViewPayloads.ShouldAllBeEquivalentTo(consensusContext.ChangeViewPayloads); }
// TODO: remove if https://github.com/neo-project/neo/issues/2061 is fixed // this logic is lifted from ConsensusService.OnPrepareRequestReceived // Log, Timer, Task and CheckPrepare logic has been commented out for offline consensus private static void OnPrepareRequestReceived(ConsensusContext context, 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 => NativeContract.Ledger.ContainsTransaction(context.Snapshot, 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 <UInt256, Transaction>(); context.VerificationContext = new TransactionVerificationContext(); for (int i = 0; i < context.PreparationPayloads.Length; i++) { if (context.PreparationPayloads[i] != null) { if (!context.GetMessage <PrepareResponse>(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 <Commit>(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 <UInt256, Transaction> mempoolVerified = Blockchain.Singleton.MemPool.GetVerifiedTransactions().ToDictionary(p => p.Hash); List <Transaction> unverified = new List <Transaction>(); foreach (UInt256 hash in context.TransactionHashes) { if (mempoolVerified.TryGetValue(hash, out var 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) // }); } bool AddTransaction(Transaction tx, bool verify) { if (verify) { VerifyResult result = tx.Verify(context.Snapshot, context.VerificationContext); if (result != VerifyResult.Succeed) { // Log($"Rejected tx: {tx.Hash}, {result}{Environment.NewLine}{tx.ToArray().ToHexString()}", LogLevel.Warning); // RequestChangeView(result == VerifyResult.PolicyFail ? ChangeViewReason.TxRejectedByPolicy : ChangeViewReason.TxInvalid); return(false); } } context.Transactions[tx.Hash] = tx; context.VerificationContext.AddTransaction(tx); return(CheckPrepareResponse()); } 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); } // TODO: Replace reflection after https://github.com/neo-project/neo-modules/pull/503 is merged // Check maximum block size via Native Contract policy var expectedBlockSize = (int)(getExpectedBlockSizeMethodInfo.Value.Invoke(context, Array.Empty <object>())) !; if (expectedBlockSize > NativeContract.Policy.GetMaxBlockSize(context.Snapshot)) { // Log($"Rejected block: {context.Block.Index} The size exceed the policy", LogLevel.Warning); // RequestChangeView(ChangeViewReason.BlockRejectedByPolicy); return(false); } // TODO: Replace duplicate code for calculating blockSystemFee after https://github.com/neo-project/neo-modules/pull/503 is merged // Check maximum block system fee via Native Contract policy var blockSystemFee = context.Transactions.Values.Sum(u => u.SystemFee); if (blockSystemFee > 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($"Sending {nameof(PrepareResponse)}"); // localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakePrepareResponse() }); // CheckPreparations(); } return(true); } }