private void AdjustLimitMillisecondsOfMiningBlock(Round currentRound, string publicKey, int nextBlockMiningLeftMilliseconds, out int limitMillisecondsOfMiningBlock) { var minerInRound = currentRound.RealTimeMinersInformation[publicKey]; var miningInterval = currentRound.GetMiningInterval(); var offset = 0; if (nextBlockMiningLeftMilliseconds < 0) { Context.LogDebug(() => "Next block mining left milliseconds is less than 0."); offset = nextBlockMiningLeftMilliseconds; } limitMillisecondsOfMiningBlock = miningInterval.Div(AEDPoSContractConstants.TotalTinySlots).Add(offset); limitMillisecondsOfMiningBlock = limitMillisecondsOfMiningBlock < 0 ? 0 : limitMillisecondsOfMiningBlock; var currentRoundStartTime = currentRound.GetStartTime(); var producedTinyBlocksForPreviousRound = minerInRound.ActualMiningTimes.Count(t => t < currentRoundStartTime); if (minerInRound.ProducedTinyBlocks == AEDPoSContractConstants.TinyBlocksNumber || minerInRound.ProducedTinyBlocks == AEDPoSContractConstants.TinyBlocksNumber.Add(producedTinyBlocksForPreviousRound)) { limitMillisecondsOfMiningBlock = limitMillisecondsOfMiningBlock.Div(2); } else { limitMillisecondsOfMiningBlock = limitMillisecondsOfMiningBlock .Mul(AEDPoSContractConstants.LimitBlockExecutionTimeWeight) .Div(AEDPoSContractConstants.LimitBlockExecutionTimeTotalShares); } }
public override Empty FirstRound(Round input) { if (State.CurrentRoundNumber.Value != 0) { return(new Empty()); } Assert(input.RoundNumber == 1, "Invalid round number."); Assert(input.RealTimeMinersInformation.Any(), "No miner in input data."); State.CurrentTermNumber.Value = 1; State.CurrentRoundNumber.Value = 1; State.FirstRoundNumberOfEachTerm[1] = 1L; SetBlockchainStartTimestamp(input.GetStartTime()); State.MiningInterval.Value = input.GetMiningInterval(); var minerList = new MinerList { Pubkeys = { input.RealTimeMinersInformation.Keys.Select(k => k.ToByteString()) } }; State.MainChainCurrentMinerList.Value = minerList; SetMinerList(minerList, 1); Assert(TryToAddRoundInformation(input), "Failed to add round information."); Context.LogDebug(() => $"Initial Miners: {input.RealTimeMinersInformation.Keys.Aggregate("\n", (key1, key2) => key1 + "\n" + key2)}"); return(new Empty()); }
/// <summary> /// The transaction with this method will generate on every node /// and executed with the same result. /// Otherwise, the block hash of the genesis block won't be equal. /// </summary> /// <param name="input"></param> /// <returns></returns> public override Empty FirstRound(Round input) { /* Basic checks. */ // Ensure the execution of the current method only happened // at the very beginning of the consensus process. if (State.CurrentRoundNumber.Value != 0) { return(new Empty()); } /* Initial settings. */ State.CurrentTermNumber.Value = 1; State.CurrentRoundNumber.Value = 1; State.FirstRoundNumberOfEachTerm[1] = 1; State.MiningInterval.Value = input.GetMiningInterval(); SetMinerList(input.GetMinerList(), 1); if (!TryToAddRoundInformation(input)) { Assert(false, "Failed to add round information."); } Context.LogDebug(() => $"Initial Miners: {input.RealTimeMinersInformation.Keys.Aggregate("\n", (key1, key2) => key1 + "\n" + key2)}"); return(new Empty()); }
private bool CheckMinerTimeSlot(Round round, string publicKey) { if (IsFirstRoundOfCurrentTerm(out _)) { return(true); } var minerInRound = round.RealTimeMinersInformation[publicKey]; var latestActualMiningTime = minerInRound.ActualMiningTimes.OrderBy(t => t).LastOrDefault(); if (latestActualMiningTime == null) { return(true); } var expectedMiningTime = minerInRound.ExpectedMiningTime; var endOfExpectedTimeSlot = expectedMiningTime.AddMilliseconds(round.GetMiningInterval()); if (latestActualMiningTime < expectedMiningTime) { // Which means this miner is producing tiny blocks for previous extra block slot. Context.LogDebug(() => $"latest actual mining time: {latestActualMiningTime}, round start time: {round.GetStartTime()}"); return(latestActualMiningTime < round.GetStartTime()); } Context.LogDebug(() => $"latest actual mining time: {latestActualMiningTime}, end of expected mining time: {endOfExpectedTimeSlot}"); return(latestActualMiningTime < endOfExpectedTimeSlot); }
/// <summary> /// We have 2 cases of producing tiny blocks: /// 1. After generating information of next round (producing extra block) /// 2. After publishing out value (producing normal block) /// </summary> /// <param name="currentRound"></param> /// <param name="publicKey"></param> /// <param name="nextBlockMiningLeftMilliseconds"></param> /// <param name="expectedMiningTime"></param> private void GetScheduleForTinyBlock(Round currentRound, string publicKey, out int nextBlockMiningLeftMilliseconds, out Timestamp expectedMiningTime) { var minerInRound = currentRound.RealTimeMinersInformation[publicKey]; var producedTinyBlocks = minerInRound.ProducedTinyBlocks; var currentRoundStartTime = currentRound.GetStartTime(); var producedTinyBlocksForPreviousRound = minerInRound.ActualMiningTimes.Count(t => t < currentRoundStartTime); var miningInterval = currentRound.GetMiningInterval(); var timeForEachBlock = miningInterval.Div(AEDPoSContractConstants.TotalTinySlots); expectedMiningTime = currentRound.GetExpectedMiningTime(publicKey); if (minerInRound.IsMinedBlockForCurrentRound()) { // After publishing out value (producing normal block) expectedMiningTime = expectedMiningTime.AddMilliseconds( currentRound.ExtraBlockProducerOfPreviousRound != publicKey ? producedTinyBlocks.Mul(timeForEachBlock) // Previous extra block producer can produce double tiny blocks at most. : producedTinyBlocks.Sub(producedTinyBlocksForPreviousRound).Mul(timeForEachBlock)); } else if (TryToGetPreviousRoundInformation(out _)) { // After generating information of next round (producing extra block) expectedMiningTime = currentRound.GetStartTime().AddMilliseconds(-miningInterval) .AddMilliseconds(producedTinyBlocks.Mul(timeForEachBlock)); } if (currentRound.RoundNumber == 1 || currentRound.RoundNumber == 2 && !minerInRound.IsMinedBlockForCurrentRound()) { nextBlockMiningLeftMilliseconds = GetNextBlockMiningLeftMillisecondsForFirstRound(minerInRound, miningInterval); } else { TuneExpectedMiningTimeForTinyBlock(miningInterval, currentRound.GetExpectedMiningTime(publicKey), ref expectedMiningTime); nextBlockMiningLeftMilliseconds = (int)(expectedMiningTime - Context.CurrentBlockTime).Milliseconds(); var toPrint = expectedMiningTime; Context.LogDebug(() => $"expected mining time: {toPrint}, current block time: {Context.CurrentBlockTime}. " + $"next: {(int) (toPrint - Context.CurrentBlockTime).Milliseconds()}"); } }
private void GetScheduleForUpdateValueWithoutPreviousInValue(Round currentRound, string publicKey, out int nextBlockMiningLeftMilliseconds, out Timestamp expectedMiningTime) { if (currentRound.RoundNumber == 1) { // To avoid initial miners fork so fast at the very beginning of current chain. nextBlockMiningLeftMilliseconds = currentRound.GetMiningOrder(publicKey).Mul(currentRound.GetMiningInterval()); expectedMiningTime = Context.CurrentBlockTime.AddMilliseconds(nextBlockMiningLeftMilliseconds); } else { // As normal as case AElfConsensusBehaviour.UpdateValue. expectedMiningTime = currentRound.GetExpectedMiningTime(publicKey); nextBlockMiningLeftMilliseconds = (int)(expectedMiningTime - Context.CurrentBlockTime).Milliseconds(); } }
/// <summary> /// The transaction with this method will generate on every node /// and executed with the same result. /// Otherwise, the block hash of the genesis block won't be equal. /// </summary> /// <param name="input"></param> /// <returns></returns> public override Empty FirstRound(Round input) { /* Basic checks. */ Assert(State.CurrentRoundNumber.Value == 0, "Already initialized."); /* Initial settings. */ State.CurrentTermNumber.Value = 1; State.CurrentRoundNumber.Value = 1; State.FirstRoundNumberOfEachTerm[1] = 1; State.MiningInterval.Value = input.GetMiningInterval(); SetMinerList(input.GetMinerList(), 1); AddRoundInformation(input); Context.LogDebug(() => $"Initial Miners: {input.RealTimeMinersInformation.Keys.Aggregate("\n", (key1, key2) => key1 + "\n" + key2)}"); return(new Empty()); }
private void GetScheduleForNextRound(Round currentRound, string publicKey, out int nextBlockMiningLeftMilliseconds, out Timestamp expectedMiningTime) { var minerInRound = currentRound.RealTimeMinersInformation[publicKey]; if (currentRound.RoundNumber == 1) { nextBlockMiningLeftMilliseconds = minerInRound.Order.Add(currentRound.RealTimeMinersInformation.Count) .Sub(1) .Mul(currentRound.GetMiningInterval()); expectedMiningTime = Context.CurrentBlockTime.AddMilliseconds(nextBlockMiningLeftMilliseconds); } else { expectedMiningTime = currentRound.ArrangeAbnormalMiningTime(minerInRound.Pubkey, Context.CurrentBlockTime); nextBlockMiningLeftMilliseconds = (int)(expectedMiningTime - Context.CurrentBlockTime).Milliseconds(); } }
/// <summary> /// AElf Consensus Behaviour is changeable in this method. /// It's the situation this miner should skip his time slot more precisely. /// </summary> /// <param name="behaviour"></param> /// <param name="currentRound"></param> /// <param name="publicKey"></param> /// <returns></returns> private ConsensusCommand GetConsensusCommand(AElfConsensusBehaviour behaviour, Round currentRound, string publicKey) { var miningInterval = currentRound.GetMiningInterval(); while (true) { var isAlone = CheckLonelyMiner(publicKey); if (behaviour == AElfConsensusBehaviour.TinyBlock && isAlone && currentRound.RealTimeMinersInformation.Count > 2 // There are more than 1 miner possible to save him. ) { behaviour = AElfConsensusBehaviour.Nothing; } var currentBlockTime = Context.CurrentBlockTime; Timestamp expectedMiningTime; int nextBlockMiningLeftMilliseconds; switch (behaviour) { case AElfConsensusBehaviour.UpdateValueWithoutPreviousInValue: GetScheduleForUpdateValueWithoutPreviousInValue(currentRound, publicKey, out nextBlockMiningLeftMilliseconds, out expectedMiningTime); break; case AElfConsensusBehaviour.UpdateValue: expectedMiningTime = currentRound.GetExpectedMiningTime(publicKey); nextBlockMiningLeftMilliseconds = (int)(expectedMiningTime - currentBlockTime).Milliseconds(); break; case AElfConsensusBehaviour.TinyBlock: GetScheduleForTinyBlock(currentRound, publicKey, out nextBlockMiningLeftMilliseconds, out expectedMiningTime); if (nextBlockMiningLeftMilliseconds < 0) { Context.LogDebug(() => "Next block mining left milliseconds is less than 0 for tiny block."); behaviour = AElfConsensusBehaviour.NextRound; continue; } break; case AElfConsensusBehaviour.NextRound: GetScheduleForNextRound(currentRound, publicKey, out nextBlockMiningLeftMilliseconds, out expectedMiningTime); break; case AElfConsensusBehaviour.NextTerm: expectedMiningTime = currentRound.ArrangeAbnormalMiningTime(publicKey, currentBlockTime); if (currentRound.RealTimeMinersInformation.Single(m => m.Value.IsExtraBlockProducer).Key != publicKey) { expectedMiningTime.AddMilliseconds(miningInterval); } nextBlockMiningLeftMilliseconds = (int)(expectedMiningTime - currentBlockTime).Milliseconds(); break; case AElfConsensusBehaviour.Nothing: return(GetInvalidConsensusCommand()); default: return(GetInvalidConsensusCommand()); } AdjustLimitMillisecondsOfMiningBlock(currentRound, publicKey, nextBlockMiningLeftMilliseconds, out var limitMillisecondsOfMiningBlock); var milliseconds = nextBlockMiningLeftMilliseconds; Context.LogDebug(() => $"NextBlockMiningLeftMilliseconds: {milliseconds}"); // Produce tiny blocks as fast as one can. if (behaviour == AElfConsensusBehaviour.TinyBlock) { nextBlockMiningLeftMilliseconds = AEDPoSContractConstants.MinimumIntervalOfProducingBlocks; } return(new ConsensusCommand { ExpectedMiningTime = expectedMiningTime, NextBlockMiningLeftMilliseconds = nextBlockMiningLeftMilliseconds < 0 ? 0 : nextBlockMiningLeftMilliseconds, LimitMillisecondsOfMiningBlock = isAlone ? currentRound.GetMiningInterval() : behaviour == AElfConsensusBehaviour.NextTerm ? miningInterval : limitMillisecondsOfMiningBlock, Hint = new AElfConsensusHint { Behaviour = behaviour }.ToByteString() }); } }