/// <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 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()); }
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> /// Get consensus behaviour if miner didn't mine block for current round. /// </summary> /// <param name="currentRound"></param> /// <param name="publicKey"></param> /// <param name="isFirstRoundOfCurrentTerm"></param> /// <returns></returns> private AElfConsensusBehaviour GetBehaviourIfMinerDidNotMineBlockForCurrentRound(Round currentRound, string publicKey, bool isFirstRoundOfCurrentTerm) { var minerInRound = currentRound.RealTimeMinersInformation[publicKey]; if (currentRound.RoundNumber == 1 && // For first round, the expected mining time is incorrect, minerInRound.Order != 1 && // so we'd better prevent miners' ain't first order (meanwhile isn't boot miner) from mining fork blocks currentRound.FirstMiner().OutValue == null // by postpone their mining time ) { return(AElfConsensusBehaviour.NextRound); } if (currentRound.ExtraBlockProducerOfPreviousRound == publicKey && // If this miner is extra block producer of previous round, Context.CurrentBlockTime < currentRound.GetStartTime() && // and currently the time is ahead of current round, minerInRound.ProducedTinyBlocks < AEDPoSContractConstants.TinyBlocksNumber // make this miner produce some tiny blocks. ) { return(AElfConsensusBehaviour.TinyBlock); } return(isFirstRoundOfCurrentTerm ? AElfConsensusBehaviour.UpdateValueWithoutPreviousInValue : AElfConsensusBehaviour.Nothing); }
public override Empty NextRound(Round input) { if (TryToGetCurrentRoundInformation(out var currentRound)) { var publicKey = Context.RecoverPublicKey().ToHex(); if (!currentRound.RealTimeMinersInformation.Keys.Contains(publicKey)) { return(new Empty()); } Assert(currentRound.RoundNumber < input.RoundNumber, "Incorrect round number for next round."); } if (currentRound.RoundNumber == 1) { var actualBlockchainStartTimestamp = input.GetStartTime(); SetBlockchainStartTimestamp(actualBlockchainStartTimestamp); if (State.IsMainChain.Value) { var minersCount = GetMinersCount(input); if (minersCount != 0 && State.ElectionContract.Value != null) { State.ElectionContract.UpdateMinersCount.Send(new UpdateMinersCountInput { MinersCount = minersCount }); } } } Assert(TryToGetCurrentRoundInformation(out _), "Failed to get current round information."); Assert(TryToAddRoundInformation(input), "Failed to add round information."); Assert(TryToUpdateRoundNumber(input.RoundNumber), "Failed to update round number."); ClearExpiredRandomNumberTokens(); return(new Empty()); }