Esempio n. 1
0
        /// <summary>
        /// In current DPoS design, each miner produce his block in one time slot, then the extra block producer
        /// produce a block to terminate current round and confirm the mining order of next round.
        /// So totally, the time of one round is:
        /// MiningInterval * MinersCount + MiningInterval.
        /// </summary>
        /// <param name="round"></param>
        /// <param name="miningInterval"></param>
        /// <returns></returns>
        public static int TotalMilliseconds(this Round round, int miningInterval = 0)
        {
            if (miningInterval == 0)
            {
                miningInterval = round.GetMiningInterval();
            }

            return(round.RealTimeMinersInformation.Count * miningInterval + miningInterval);
        }
Esempio n. 2
0
        /// <summary>
        /// This method for now is able to handle the situation of a miner keeping offline so many rounds,
        /// by using missedRoundsCount.
        /// </summary>
        /// <param name="round"></param>
        /// <param name="miningInterval"></param>
        /// <param name="missedRoundsCount"></param>
        /// <returns></returns>
        public static Timestamp GetExpectedEndTime(this Round round, int missedRoundsCount = 0, int miningInterval = 0)
        {
            if (miningInterval == 0)
            {
                miningInterval = round.GetMiningInterval();
            }

            return(round.GetStartTime().AddMilliseconds(round.TotalMilliseconds(miningInterval))
                   // Arrange an ending time if this node missed so many rounds.
                   .AddMilliseconds(missedRoundsCount * round.TotalMilliseconds(miningInterval))
                   .ToTimestamp());
        }
Esempio n. 3
0
        /// <summary>
        /// For now, if current time is behind the half of expected mining time slot,
        /// we can say this node missed his time slot.
        /// </summary>
        /// <param name="round"></param>
        /// <param name="publicKey"></param>
        /// <param name="dateTime"></param>
        /// <param name="minerInRound"></param>
        /// <returns></returns>
        public static bool IsTimeSlotPassed(this Round round, string publicKey, DateTime dateTime,
                                            out MinerInRound minerInRound)
        {
            minerInRound = null;
            var miningInterval = round.GetMiningInterval();

            if (round.RealTimeMinersInformation.ContainsKey(publicKey))
            {
                minerInRound = round.RealTimeMinersInformation[publicKey];
                return(minerInRound.ExpectedMiningTime.ToDateTime().AddMilliseconds((double)miningInterval / 2) <
                       dateTime);
            }

            return(false);
        }
Esempio n. 4
0
        private void InitialSettings(Round firstRound)
        {
            State.CurrentTermNumberField.Value  = 1;
            State.CurrentRoundNumberField.Value = 1;
            State.AgeField.Value = 1;
            State.TermToFirstRoundMap[1L.ToInt64Value()] = 1L.ToInt64Value();
            SetBlockchainStartTimestamp(firstRound.GetStartTime().ToTimestamp());
            State.MiningIntervalField.Value = firstRound.GetMiningInterval();

            SetInitialMinersAliases(firstRound.RealTimeMinersInformation.Keys);
            SetInitialMinersHistory(firstRound.RealTimeMinersInformation.Keys);

            var miners = firstRound.RealTimeMinersInformation.Keys.ToList().ToMiners(1);

            miners.TermNumber = 1;
            SetMiners(miners);
        }
Esempio n. 5
0
        /// <summary>
        /// If one node produced block this round or missed his time slot,
        /// whatever how long he missed, we can give him a consensus command with new time slot
        /// to produce a block (for terminating current round and start new round).
        /// The schedule generated by this command will be cancelled
        /// if this node executed blocks from other nodes.
        ///
        /// Notice:
        /// This method shouldn't return the expected mining time from round information.
        /// To prevent this kind of misuse, this method will return a invalid timestamp
        /// when this node hasn't missed his time slot.
        /// </summary>
        /// <returns></returns>
        public static Timestamp ArrangeAbnormalMiningTime(this Round round, string publicKey, DateTime dateTime,
                                                          int miningInterval = 0)
        {
            if (!round.RealTimeMinersInformation.ContainsKey(publicKey))
            {
                return(DateTime.MaxValue.ToUniversalTime().ToTimestamp());
            }

            if (miningInterval == 0)
            {
                miningInterval = round.GetMiningInterval();
            }

            if (!round.IsTimeSlotPassed(publicKey, dateTime, out var minerInRound) && minerInRound.OutValue == null)
            {
                return(DateTime.MaxValue.ToUniversalTime().ToTimestamp());
            }

            if (round.GetExtraBlockProducerInformation().PublicKey == publicKey)
            {
                var distance = (round.GetExtraBlockMiningTime() - dateTime).TotalMilliseconds;
                if (distance > 0)
                {
                    return(round.GetExtraBlockMiningTime().ToTimestamp());
                }
            }

            if (round.RealTimeMinersInformation.ContainsKey(publicKey) && miningInterval > 0)
            {
                var distanceToRoundStartTime = (dateTime - round.GetStartTime()).TotalMilliseconds;
                var missedRoundsCount        = (int)(distanceToRoundStartTime / round.TotalMilliseconds(miningInterval));
                var expectedEndTime          = round.GetExpectedEndTime(missedRoundsCount, miningInterval);
                return(expectedEndTime.ToDateTime().AddMilliseconds(minerInRound.Order * miningInterval).ToTimestamp());
            }

            // Never do the mining if this node has no privilege to mime or the mining interval is invalid.
            return(DateTime.MaxValue.ToUniversalTime().ToTimestamp());
        }
Esempio n. 6
0
 public static DateTime GetExtraBlockMiningTime(this Round round)
 {
     return(round.RealTimeMinersInformation.OrderBy(m => m.Value.ExpectedMiningTime.ToDateTime()).Last().Value
            .ExpectedMiningTime.ToDateTime()
            .AddMilliseconds(round.GetMiningInterval()));
 }
Esempio n. 7
0
        public static bool GenerateNextRoundInformation(this Round currentRound, DateTime dateTime,
                                                        Timestamp blockchainStartTimestamp, out Round nextRound)
        {
            nextRound = new Round();

            var minersMinedCurrentRound    = currentRound.GetMinedMiners();
            var minersNotMinedCurrentRound = currentRound.GetNotMinedMiners();
            var minersCount = currentRound.RealTimeMinersInformation.Count;

            var miningInterval = currentRound.GetMiningInterval();

            nextRound.RoundNumber = currentRound.RoundNumber + 1;
            nextRound.TermNumber  = currentRound.TermNumber;
            if (currentRound.RoundNumber == 1)
            {
                nextRound.BlockchainAge = 1;
            }
            else
            {
                nextRound.BlockchainAge =
                    (long)(dateTime - blockchainStartTimestamp.ToDateTime())
                    .TotalMinutes; // TODO: Change to TotalDays after testing.
            }

            // Set next round miners' information of miners who successfully mined during this round.
            foreach (var minerInRound in minersMinedCurrentRound.OrderBy(m => m.FinalOrderOfNextRound))
            {
                var order = minerInRound.FinalOrderOfNextRound;
                nextRound.RealTimeMinersInformation[minerInRound.PublicKey] = new MinerInRound
                {
                    PublicKey          = minerInRound.PublicKey,
                    Order              = order,
                    ExpectedMiningTime = dateTime.ToTimestamp().GetArrangedTimestamp(order, miningInterval),
                    PromisedTinyBlocks = minerInRound.PromisedTinyBlocks,
                    ProducedBlocks     = minerInRound.ProducedBlocks,
                    MissedTimeSlots    = minerInRound.MissedTimeSlots
                };
            }

            // Set miners' information of miners missed their time slot in current round.
            var occupiedOrders = minersMinedCurrentRound.Select(m => m.FinalOrderOfNextRound).ToList();
            var ableOrders     = Enumerable.Range(1, minersCount).Where(i => !occupiedOrders.Contains(i)).ToList();

            for (var i = 0; i < minersNotMinedCurrentRound.Count; i++)
            {
                var order        = ableOrders[i];
                var minerInRound = minersNotMinedCurrentRound[i];
                nextRound.RealTimeMinersInformation[minerInRound.PublicKey] = new MinerInRound
                {
                    PublicKey          = minersNotMinedCurrentRound[i].PublicKey,
                    Order              = order,
                    ExpectedMiningTime = dateTime.ToTimestamp().GetArrangedTimestamp(order, miningInterval),
                    PromisedTinyBlocks = minerInRound.PromisedTinyBlocks,
                    ProducedBlocks     = minerInRound.ProducedBlocks,
                    MissedTimeSlots    = minerInRound.MissedTimeSlots + 1
                };
            }

            // Calculate extra block producer order and set the producer.
            var extraBlockProducerOrder    = currentRound.CalculateNextExtraBlockProducerOrder();
            var expectedExtraBlockProducer =
                nextRound.RealTimeMinersInformation.Values.FirstOrDefault(m => m.Order == extraBlockProducerOrder);

            if (expectedExtraBlockProducer == null)
            {
                nextRound.RealTimeMinersInformation.Values.First().IsExtraBlockProducer = true;
            }
            else
            {
                expectedExtraBlockProducer.IsExtraBlockProducer = true;
            }

            return(true);
        }
Esempio n. 8
0
        /// <summary>
        /// DPoS 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="round"></param>
        /// <param name="publicKey"></param>
        /// <param name="dateTime"></param>
        /// <param name="isTimeSlotSkippable"></param>
        /// <returns></returns>
        internal static ConsensusCommand GetConsensusCommand(this DPoSBehaviour behaviour, Round round, string publicKey,
                                                             DateTime dateTime, bool isTimeSlotSkippable)
        {
            var minerInRound       = round.RealTimeMinersInformation[publicKey];
            var miningInterval     = round.GetMiningInterval();
            var myOrder            = round.RealTimeMinersInformation[minerInRound.PublicKey].Order;
            var expectedMiningTime = round.RealTimeMinersInformation[minerInRound.PublicKey].ExpectedMiningTime;

            int nextBlockMiningLeftMilliseconds;
            var hint = new DPoSHint {
                Behaviour = behaviour
            }.ToByteString();

            var previousMinerMissedHisTimeSlot       = myOrder != 1 &&
                                                       round.RealTimeMinersInformation.Values
                                                       .First(m => m.Order == myOrder - 1).OutValue == null;
            var previousTwoMinersMissedTheirTimeSlot = myOrder > 2 &&
                                                       round.RealTimeMinersInformation.Values
                                                       .First(m => m.Order == myOrder - 1).OutValue == null &&
                                                       round.RealTimeMinersInformation.Values
                                                       .First(m => m.Order == myOrder - 2).OutValue == null;
            var skipTimeSlot = previousMinerMissedHisTimeSlot && !previousTwoMinersMissedTheirTimeSlot &&
                               isTimeSlotSkippable;

            var firstMinerOfCurrentRound =
                round.RealTimeMinersInformation.Values.FirstOrDefault(m => m.OutValue != null);

            switch (behaviour)
            {
            case DPoSBehaviour.UpdateValueWithoutPreviousInValue:
                // Two reasons of `UpdateValueWithoutPreviousInValue` behaviour:
                // 1. 1st round of 1st term.
                // 2. Term changed in current round.
                if (skipTimeSlot)
                {
                    if (firstMinerOfCurrentRound != null)
                    {
                        var roundStartTimeInTheory = firstMinerOfCurrentRound.ActualMiningTime.ToDateTime()
                                                     .AddMilliseconds(-firstMinerOfCurrentRound.Order * miningInterval);
                        var minersCount = round.RealTimeMinersInformation.Count;
                        var extraBlockMiningTimeInTheory =
                            roundStartTimeInTheory.AddMilliseconds(minersCount * miningInterval);
                        nextBlockMiningLeftMilliseconds =
                            (int)(round.ArrangeAbnormalMiningTime(publicKey, extraBlockMiningTimeInTheory,
                                                                  miningInterval).ToDateTime() - dateTime).TotalMilliseconds;
                        // If someone produced block in current round before.

                        hint = new DPoSHint
                        {
                            Behaviour = DPoSBehaviour.NextRound
                        }.ToByteString();
                        break;
                    }

                    nextBlockMiningLeftMilliseconds = minerInRound.Order * miningInterval * 2 + miningInterval;
                    hint = new DPoSHint
                    {
                        Behaviour = DPoSBehaviour.NextRound
                    }.ToByteString();
                    break;
                }

                nextBlockMiningLeftMilliseconds = minerInRound.Order * miningInterval;
                break;

            case DPoSBehaviour.UpdateValue:
                // If miner of previous order didn't produce block, skip this time slot.
                if (skipTimeSlot)
                {
                    nextBlockMiningLeftMilliseconds = (int)(round.ArrangeAbnormalMiningTime(minerInRound.PublicKey,
                                                                                            round.GetExtraBlockMiningTime(),
                                                                                            round.GetMiningInterval()).ToDateTime() - dateTime)
                                                      .TotalMilliseconds;
                    hint = new DPoSHint
                    {
                        Behaviour = DPoSBehaviour.NextRound
                    }.ToByteString();
                    break;
                }

                nextBlockMiningLeftMilliseconds =
                    (int)(expectedMiningTime.ToDateTime() - dateTime).TotalMilliseconds;
                break;

            case DPoSBehaviour.NextRound:
                nextBlockMiningLeftMilliseconds = round.RoundNumber == 1
                        ? round.RealTimeMinersInformation.Count * miningInterval + myOrder * miningInterval
                        : (int)(round.ArrangeAbnormalMiningTime(minerInRound.PublicKey, dateTime).ToDateTime() -
                                dateTime).TotalMilliseconds;
                break;

            case DPoSBehaviour.NextTerm:
                nextBlockMiningLeftMilliseconds =
                    (int)(round.ArrangeAbnormalMiningTime(minerInRound.PublicKey, dateTime).ToDateTime() -
                          dateTime).TotalMilliseconds;
                break;

            default:
                return(new ConsensusCommand
                {
                    ExpectedMiningTime = expectedMiningTime,
                    NextBlockMiningLeftMilliseconds = int.MaxValue,
                    LimitMillisecondsOfMiningBlock = int.MaxValue,
                    Hint = new DPoSHint
                    {
                        Behaviour = DPoSBehaviour.Nothing
                    }.ToByteString()
                });
            }

            return(new ConsensusCommand
            {
                ExpectedMiningTime = expectedMiningTime,
                NextBlockMiningLeftMilliseconds = nextBlockMiningLeftMilliseconds,
                LimitMillisecondsOfMiningBlock = miningInterval / minerInRound.PromisedTinyBlocks,
                Hint = hint
            });
        }