예제 #1
0
        public override ActionResult QuitElection(Empty input)
        {
            var candidates = State.CandidatesField.Value;

            Assert(candidates != null,
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidField, nameof(State.CandidatesField)));

            var publicKey = Context.RecoverPublicKey().ToHex();

            Assert(candidates != null && candidates.PublicKeys.Contains(publicKey),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidOperation, "Not announced."));

            Assert(candidates != null && candidates.RemoveCandidate(Context.RecoverPublicKey()),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.AttemptFailed,
                                                     "Failed to remove this public key from candidates list."));

            State.CandidatesField.Value = candidates;

            State.TokenContract.Unlock.Send(new UnlockInput
            {
                From   = Context.Sender,
                To     = Context.Self,
                Symbol = Context.Variables.NativeSymbol,
                LockId = State.HistoryMap[publicKey.ToStringValue()].AnnouncementTransactionId,
                Amount = State.LockTokenForElection.Value,
                Usage  = "Unlock and quit election."
            });

            return(new ActionResult {
                Success = true
            });
        }
예제 #2
0
        public override Empty InitialDPoSContract(InitialDPoSContractInput input)
        {
            Assert(!State.Initialized.Value,
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidOperation, "Already initialized."));

            State.TokenContractSystemName.Value    = input.TokenContractSystemName;
            State.DividendContractSystemName.Value = input.DividendsContractSystemName;
            State.LockTokenForElection.Value       = input.LockTokenForElection;

            State.Initialized.Value = true;

            return(new Empty());
        }
예제 #3
0
        /// <summary>
        /// Initial miners can set blockchain age manually.
        /// For testing.
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public override Empty SetBlockchainAge(SInt64Value input)
        {
            Assert(TryToGetRoundInformation(1, out var firstRound),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.NotFound, "Failed to get first round information"));
            Assert(firstRound.RealTimeMinersInformation.Keys.Contains(Context.RecoverPublicKey().ToHex()),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.NoPermission,
                                                     "No permission to change blockchain age."));
            // Don't use `UpdateBlockchainAge` here. Because in testing, we can set blockchain age for free.
            State.AgeField.Value = input.Value;

            LogVerbose($"{Context.RecoverPublicKey().ToHex()} set blockchain age to {input.Value}");

            return(new Empty());
        }
예제 #4
0
        public override Empty InitialConsensus(Round firstRound)
        {
            Assert(firstRound.RoundNumber == 1,
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidField, "Invalid round number."));

            Assert(firstRound.RealTimeMinersInformation.Any(),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidField, "No miner in input data."));

            InitialSettings(firstRound);

            Assert(TryToAddRoundInformation(firstRound),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.AttemptFailed, "Failed to add round information."));

            State.BasicContractZero.Value = Context.GetZeroSmartContractAddress();

            return(new Empty());
        }
예제 #5
0
        public override Empty ConfigStrategy(DPoSStrategyInput input)
        {
            Assert(!State.IsStrategyConfigured.Value,
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidOperation, "Already configured."));

            State.IsBlockchainAgeSettable.Value = input.IsBlockchainAgeSettable;
            State.IsTimeSlotSkippable.Value     = input.IsTimeSlotSkippable;
            State.IsVerbose.Value = input.IsVerbose;

            State.IsStrategyConfigured.Value = true;

            LogVerbose("Consensus log level: Verbose");
            LogVerbose($"Is blockchain age settable: {input.IsBlockchainAgeSettable}");
            LogVerbose($"Is time slot skippable: {input.IsTimeSlotSkippable}");

            return(new Empty());
        }
예제 #6
0
        // ReSharper disable once PossibleNullReferenceException
        public override ActionResult ReceiveDividendsByTransactionId(Hash input)
        {
            var txId         = input;
            var votingRecord = State.VotingRecordsMap[txId];

            Assert(votingRecord != null,
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.NotFound, "Voting record not found."));

            Assert(votingRecord.From == Context.RecoverPublicKey().ToHex(),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.NoPermission,
                                                     "No permission to receive."));

            State.DividendContract.TransferDividends.Send(
                Dividend.VotingRecord.Parser.ParseFrom(votingRecord.ToByteString()));

            return(new ActionResult {
                Success = true
            });
        }
예제 #7
0
        // ReSharper disable once PossibleNullReferenceException
        public override ActionResult ReceiveAllDividends(Empty input)
        {
            var tickets = State.TicketsMap[Context.RecoverPublicKey().ToHex().ToStringValue()];

            Assert(tickets != null,
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.NotFound, "Tickets information not found."));

            Assert(tickets.VoteToTransactions.Any(),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.NotFound, "Voting records not found."));

            foreach (var transactionId in tickets.VoteToTransactions)
            {
                var votingRecord = State.VotingRecordsMap[transactionId];
                Assert(votingRecord != null,
                       ContractErrorCode.GetErrorMessage(ContractErrorCode.NotFound, "Voting record not found."));
                State.DividendContract.TransferDividends.Send(
                    Dividend.VotingRecord.Parser.ParseFrom(votingRecord.ToByteString()));
            }

            return(new ActionResult {
                Success = true
            });
        }
예제 #8
0
        // ReSharper disable PossibleNullReferenceException
        public override Tickets WithdrawAll(Empty input)
        {
            var voterPublicKey      = Context.RecoverPublicKey().ToHex();
            var ticketsCount        = State.TicketsCountField.Value;
            var withdrawnAmount     = 0L;
            var candidatesVotesDict = new Dictionary <string, long>();

            var tickets = State.TicketsMap[voterPublicKey.ToStringValue()];

            Assert(tickets != null,
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.NotFound, "Tickets information not found."));

            foreach (var transactionId in tickets.VoteToTransactions)
            {
                var votingRecord = State.VotingRecordsMap[transactionId];

                Assert(votingRecord != null,
                       ContractErrorCode.GetErrorMessage(ContractErrorCode.NotFound, "Voting record not found."));

                if (votingRecord.UnlockAge > CurrentAge)
                {
                    // Just check next one, no need to assert.
                    continue;
                }

                // Update voting record map.
                TryToGetBlockchainStartTimestamp(out var blockchainStartTimestamp);
                votingRecord.WithdrawTimestamp =
                    blockchainStartTimestamp.ToDateTime().AddMinutes(CurrentAge).ToTimestamp();
                votingRecord.IsWithdrawn = true;
                State.VotingRecordsMap[votingRecord.TransactionId] = votingRecord;

                // Prepare data for updating tickets map.
                withdrawnAmount += votingRecord.Count;
                if (candidatesVotesDict.ContainsKey(votingRecord.To))
                {
                    candidatesVotesDict[votingRecord.To] += votingRecord.Count;
                }
                else
                {
                    candidatesVotesDict.Add(votingRecord.To, votingRecord.Count);
                }

                State.TokenContract.Unlock.Send(new UnlockInput
                {
                    From   = Context.Sender,
                    To     = Context.Self,
                    Symbol = Context.Variables.NativeSymbol,
                    Amount = votingRecord.Count,
                    LockId = votingRecord.TransactionId,
                    Usage  = $"Withdraw locked token of transaction {transactionId}: {votingRecord}"
                });

                State.DividendContract.SubWeights.Send(
                    new Dividend.WeightsInfo()
                {
                    TermNumber = State.CurrentTermNumberField.Value,
                    Weights    = votingRecord.Weight
                });
            }

            ticketsCount -= withdrawnAmount;
            State.TicketsCountField.Value = ticketsCount;

            tickets.VotedTickets -= withdrawnAmount;
            State.TicketsMap[voterPublicKey.ToStringValue()] = tickets;

            foreach (var candidateVote in candidatesVotesDict)
            {
                var ticketsOfCandidate = State.TicketsMap[candidateVote.Key.ToStringValue()];
                Assert(ticketsOfCandidate != null,
                       ContractErrorCode.GetErrorMessage(ContractErrorCode.NotFound,
                                                         $"Tickets information of {candidateVote.Key} not found."));
                ticketsOfCandidate.ObtainedTickets -= candidateVote.Value;
                State.TicketsMap[candidateVote.Key.ToStringValue()] = ticketsOfCandidate;
            }

            return(State.TicketsMap[voterPublicKey.ToStringValue()]);
        }
예제 #9
0
        // ReSharper disable once PossibleNullReferenceException
        public override Tickets WithdrawByTransactionId(Hash input)
        {
            var txId         = input;
            var votingRecord = State.VotingRecordsMap[txId];

            Assert(votingRecord != null,
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.NotFound, "Voting record not found."));

            Assert(!votingRecord.IsWithdrawn,
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidOperation,
                                                     "This voting record has already withdrawn."));

            Assert(votingRecord.UnlockAge <= CurrentAge,
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidOperation,
                                                     "This voting record can't withdraw for now."));

            Assert(votingRecord.From == Context.RecoverPublicKey().ToHex(),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.NoPermission,
                                                     "No permission to withdraw tickets of others."));

            // Update voting record map.
            TryToGetBlockchainStartTimestamp(out var blockchainStartTimestamp);
            votingRecord.WithdrawTimestamp =
                blockchainStartTimestamp.ToDateTime().AddMinutes(CurrentAge).ToTimestamp();
            votingRecord.IsWithdrawn     = true;
            State.VotingRecordsMap[txId] = votingRecord;

            // Update total tickets count.
            var ticketsCount = State.TicketsCountField.Value;

            ticketsCount -= votingRecord.Count;
            State.TicketsCountField.Value = ticketsCount;

            // Update tickets number of this voter.
            var ticketsOfVoter = State.TicketsMap[votingRecord.From.ToStringValue()];

            if (ticketsOfVoter != null)
            {
                ticketsOfVoter.VotedTickets -= votingRecord.Count;
                State.TicketsMap[votingRecord.From.ToStringValue()] = ticketsOfVoter;
            }

            // Update tickets number of related candidate.
            var ticketsOfCandidate = State.TicketsMap[votingRecord.To.ToStringValue()];

            if (ticketsOfCandidate != null)
            {
                ticketsOfCandidate.ObtainedTickets -= votingRecord.Count;
                State.TicketsMap[votingRecord.To.ToStringValue()] = ticketsOfCandidate;
            }

            // Sub weight.
            State.DividendContract.SubWeights.Send(
                new Dividend.WeightsInfo()
            {
                TermNumber = State.CurrentTermNumberField.Value,
                Weights    = votingRecord.Weight
            });
            // Transfer token back to voter.
            State.TokenContract.Unlock.Send(new UnlockInput
            {
                From   = Context.Sender,
                To     = Context.Self,
                Symbol = Context.Variables.NativeSymbol,
                Amount = votingRecord.Count,
                LockId = votingRecord.TransactionId,
                Usage  = $"Withdraw locked token of transaction {txId.ToHex()}: {votingRecord}"
            });

            return(State.TicketsMap[votingRecord.From.ToStringValue()]);
        }
예제 #10
0
        public override ActionResult AnnounceElection(Alias input)
        {
            if (State.DividendContract.Value == null)
            {
                State.DividendContract.Value =
                    State.BasicContractZero.GetContractAddressByName.Call(State.DividendContractSystemName.Value);
            }
            if (State.TokenContract.Value == null)
            {
                State.TokenContract.Value =
                    State.BasicContractZero.GetContractAddressByName.Call(State.TokenContractSystemName.Value);
            }
            var alias     = input.Value;
            var publicKey = Context.RecoverPublicKey().ToHex();
            // A voter cannot join the election before all his voting record expired.
            var tickets = State.TicketsMap[publicKey.ToStringValue()];

            if (tickets != null)
            {
                foreach (var voteToTransaction in tickets.VoteToTransactions)
                {
                    var votingRecord = State.VotingRecordsMap[voteToTransaction];
                    if (votingRecord != null)
                    {
                        Assert(votingRecord.IsWithdrawn,
                               ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidOperation,
                                                                 "Voter can't announce election."));
                    }
                }
            }

            var candidates = State.CandidatesField.Value;

            if (candidates != null)
            {
                Assert(!candidates.PublicKeys.Contains(publicKey),
                       ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidOperation,
                                                         "Already announced election."));
                candidates.AddCandidate(Context.RecoverPublicKey());
            }
            else
            {
                candidates = new Candidates
                {
                    PublicKeys = { publicKey },
                    Addresses  = { Address.FromPublicKey(Context.RecoverPublicKey()) }
                };
            }

            State.CandidatesField.Value = candidates;

            if (alias == "" || alias.Length > DPoSContractConsts.AliasLimit)
            {
                alias = publicKey.Substring(0, DPoSContractConsts.AliasLimit);
            }

            var publicKeyOfThisAlias = State.AliasesLookupMap[alias.ToStringValue()];

            if (publicKeyOfThisAlias != null &&
                publicKey == publicKeyOfThisAlias.Value)
            {
                return(new ActionResult {
                    Success = true
                });
            }

            State.AliasesLookupMap[alias.ToStringValue()] = publicKey.ToStringValue();
            State.AliasesMap[publicKey.ToStringValue()]   = alias.ToStringValue();

            // Add this alias to history information of this candidate.
            var candidateHistoryInformation = State.HistoryMap[publicKey.ToStringValue()];

            if (candidateHistoryInformation != null)
            {
                if (!candidateHistoryInformation.Aliases.Contains(alias))
                {
                    candidateHistoryInformation.Aliases.Add(alias);
                    candidateHistoryInformation.CurrentAlias = alias;
                }

                candidateHistoryInformation.AnnouncementTransactionId = Context.TransactionId;
                State.HistoryMap[publicKey.ToStringValue()]           = candidateHistoryInformation;
            }
            else
            {
                State.HistoryMap[publicKey.ToStringValue()] = new CandidateInHistory
                {
                    CurrentAlias = alias,
                    AnnouncementTransactionId = Context.TransactionId
                };
            }

            State.TokenContract.Lock.Send(new LockInput()
            {
                From   = Context.Sender,
                To     = Context.Self,
                Symbol = Context.Variables.NativeSymbol,
                Amount = State.LockTokenForElection.Value,
                LockId = Context.TransactionId,
                Usage  = "Lock for announcing election."
            });

            return(new ActionResult {
                Success = true
            });
        }
예제 #11
0
        public override Hash Vote(VoteInput input)
        {
            var candidatePublicKey = input.CandidatePublicKey;
            var amount             = input.Amount;
            var lockTime           = input.LockTime;

            Assert(lockTime.InRange(90, 1095),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidOperation, "Lock days is illegal."));

            // Cannot vote to non-candidate.
            var candidates = State.CandidatesField.Value;

            Assert(candidates != null, ContractErrorCode.GetErrorMessage(ContractErrorCode.NotFound, "No candidate."));
            Assert(candidates != null && candidates.PublicKeys.Contains(candidatePublicKey),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidOperation,
                                                     "Target didn't announce election."));

            var voterPublicKey = Context.RecoverPublicKey().ToHex();

            // A candidate cannot vote to anybody.
            Assert(candidates != null && !candidates.PublicKeys.Contains(voterPublicKey),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.InvalidOperation, "Candidate can't vote."));

            // Transfer the tokens to Consensus Contract address.
            State.TokenContract.Lock.Send(new LockInput
            {
                From   = Context.Sender,
                To     = Context.Self,
                Symbol = Context.Variables.NativeSymbol,
                Amount = amount,
                LockId = Context.TransactionId,
                Usage  = "Lock for getting tickets."
            });

            var currentTermNumber  = State.CurrentTermNumberField.Value;
            var currentRoundNumber = State.CurrentRoundNumberField.Value;

            // To make up a VotingRecord instance.
            TryToGetBlockchainStartTimestamp(out var blockchainStartTimestamp);

            var votingRecord = new VotingRecord
            {
                Count           = amount,
                From            = voterPublicKey,
                To              = candidatePublicKey,
                RoundNumber     = currentRoundNumber,
                TransactionId   = Context.TransactionId,
                VoteAge         = CurrentAge,
                UnlockAge       = CurrentAge + (long)lockTime,
                TermNumber      = currentTermNumber,
                VoteTimestamp   = blockchainStartTimestamp.ToDateTime().AddMinutes(CurrentAge).ToTimestamp(),
                UnlockTimestamp = blockchainStartTimestamp.ToDateTime().AddMinutes(CurrentAge + (long)lockTime)
                                  .ToTimestamp()
            };

            votingRecord.LockDaysList.Add(lockTime);

            // Add the transaction id of this voting record to the tickets information of the voter.
            var tickets = State.TicketsMap[voterPublicKey.ToStringValue()];

            if (tickets != null)
            {
                tickets.VoteToTransactions.Add(votingRecord.TransactionId);
            }
            else
            {
                tickets = new Tickets {
                    PublicKey = voterPublicKey
                };
                tickets.VoteToTransactions.Add(votingRecord.TransactionId);
            }

            tickets.VotedTickets        += votingRecord.Count;
            tickets.HistoryVotedTickets += votingRecord.Count;
            State.TicketsMap[voterPublicKey.ToStringValue()] = tickets;

            // Add the transaction id of this voting record to the tickets information of the candidate.
            var candidateTickets = State.TicketsMap[candidatePublicKey.ToStringValue()];

            if (candidateTickets != null)
            {
                candidateTickets.VoteFromTransactions.Add(votingRecord.TransactionId);
            }
            else
            {
                candidateTickets = new Tickets {
                    PublicKey = candidatePublicKey
                };
                candidateTickets.VoteFromTransactions.Add(votingRecord.TransactionId);
            }

            candidateTickets.ObtainedTickets        += votingRecord.Count;
            candidateTickets.HistoryObtainedTickets += votingRecord.Count;
            State.TicketsMap[candidatePublicKey.ToStringValue()] = candidateTickets;

            // Update the amount of votes (voting records of whole system).
            State.VotesCountField.Value += 1;

            // Update the amount of tickets.
            State.TicketsCountField.Value += votingRecord.Count;

            // Add this voting record to voting records map.
            State.VotingRecordsMap[votingRecord.TransactionId] = votingRecord;

            // Tell Dividends Contract to add weights for this voting record.
            State.DividendContract.AddWeights.Send(new Dividend.WeightsInfo()
            {
                TermNumber = currentTermNumber + 1, Weights = votingRecord.Weight
            });

            Context.LogDebug(() => $"Weights of vote {votingRecord.TransactionId.ToHex()}: {votingRecord.Weight}");

            return(Context.TransactionId);
        }
예제 #12
0
        public override DPoSHeaderInformation GetInformationToUpdateConsensus(DPoSTriggerInformation input)
        {
            // Some basic checks.
            Assert(input.PublicKey.Any(), "Invalid public key.");

            var publicKey        = input.PublicKey;
            var currentBlockTime = Context.CurrentBlockTime;
            var behaviour        = input.Behaviour;

            Assert(TryToGetCurrentRoundInformation(out var currentRound),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.AttemptFailed,
                                                     "Failed to get current round information."));

            switch (behaviour)
            {
            case DPoSBehaviour.UpdateValueWithoutPreviousInValue:
            case DPoSBehaviour.UpdateValue:
                Assert(input.RandomHash != null, "Random hash should not be null.");

                var inValue         = currentRound.CalculateInValue(input.RandomHash);
                var outValue        = Hash.FromMessage(inValue);
                var signature       = Hash.FromTwoHashes(outValue, input.RandomHash); // Just initial signature value.
                var previousInValue = Hash.Empty;                                     // Just initial previous in value.

                if (TryToGetPreviousRoundInformation(out var previousRound) && !IsJustChangedTerm(out _))
                {
                    signature = previousRound.CalculateSignature(inValue);
                    LogVerbose($"Previous random hash: {input.PreviousRandomHash.ToHex()}");
                    if (input.PreviousRandomHash != Hash.Empty)
                    {
                        // If PreviousRandomHash is Hash.Empty, it means the sender unable or unwilling to publish his previous in value.
                        previousInValue = previousRound.CalculateInValue(input.PreviousRandomHash);
                    }
                }

                var updatedRound = currentRound.ApplyNormalConsensusData(publicKey.ToHex(), previousInValue,
                                                                         outValue, signature, currentBlockTime);

                ShareAndRecoverInValue(updatedRound, previousRound, inValue, publicKey.ToHex());

                // To publish Out Value.
                return(new DPoSHeaderInformation
                {
                    SenderPublicKey = publicKey,
                    Round = updatedRound,
                    Behaviour = behaviour,
                });

            case DPoSBehaviour.NextRound:
                Assert(
                    GenerateNextRoundInformation(currentRound, currentBlockTime, out var nextRound),
                    "Failed to generate next round information.");
                nextRound.RealTimeMinersInformation[publicKey.ToHex()].ProducedBlocks += 1;
                Context.LogDebug(() => $"Mined blocks: {nextRound.GetMinedBlocks()}");
                nextRound.ExtraBlockProducerOfPreviousRound = publicKey.ToHex();
                return(new DPoSHeaderInformation
                {
                    SenderPublicKey = publicKey,
                    Round = nextRound,
                    Behaviour = behaviour
                });

            case DPoSBehaviour.NextTerm:
                var firstRoundOfNextTerm = GenerateFirstRoundOfNextTerm(publicKey.ToHex());
                Assert(firstRoundOfNextTerm.RoundId != 0, "Failed to generate new round information.");
                var information = new DPoSHeaderInformation
                {
                    SenderPublicKey = publicKey,
                    Round           = firstRoundOfNextTerm,
                    Behaviour       = behaviour
                };
                return(information);

            default:
                return(new DPoSHeaderInformation());
            }
        }
예제 #13
0
        public override Empty NextTerm(Round input)
        {
            // Count missed time slot of current round.
            CountMissedTimeSlots();

            Assert(TryToGetTermNumber(out var termNumber), "Term number not found.");

            // Update current term number and current round number.
            Assert(TryToUpdateTermNumber(input.TermNumber), "Failed to update term number.");
            Assert(TryToUpdateRoundNumber(input.RoundNumber), "Failed to update round number.");

            // Reset some fields of first two rounds of next term.
            foreach (var minerInRound in input.RealTimeMinersInformation.Values)
            {
                minerInRound.MissedTimeSlots = 0;
                minerInRound.ProducedBlocks  = 0;
            }

            var senderPublicKey = Context.RecoverPublicKey().ToHex();

            // Update produced block number of this node.
            if (input.RealTimeMinersInformation.ContainsKey(senderPublicKey))
            {
                input.RealTimeMinersInformation[senderPublicKey].ProducedBlocks += 1;
            }
            else
            {
                if (TryToGetMinerHistoryInformation(senderPublicKey, out var historyInformation))
                {
                    historyInformation.ProducedBlocks += 1;
                }
                else
                {
                    historyInformation = new CandidateInHistory
                    {
                        PublicKey      = senderPublicKey,
                        ProducedBlocks = 1,
                        CurrentAlias   = senderPublicKey.Substring(0, DPoSContractConsts.AliasLimit)
                    };
                }

                AddOrUpdateMinerHistoryInformation(historyInformation);
            }

            // Update miners list.
            Assert(SetMiners(input.RealTimeMinersInformation.Keys.ToList().ToMiners(input.TermNumber)),
                   ContractErrorCode.GetErrorMessage(ContractErrorCode.AttemptFailed, "Failed to update miners list."));

            // Update term number lookup. (Using term number to get first round number of related term.)
            State.TermToFirstRoundMap[input.TermNumber.ToInt64Value()] = input.RoundNumber.ToInt64Value();

            // Update blockchain age of new term.
            UpdateBlockchainAge(input.BlockchainAge);

            // Update rounds information of next two rounds.
            Assert(TryToAddRoundInformation(input), "Failed to add round information.");

            if (State.DividendContract.Value != null)
            {
                State.DividendContract.Value =
                    State.BasicContractZero.GetContractAddressByName.Call(State.DividendContractSystemName.Value);
                State.DividendContract.KeepWeights.Send(new SInt64Value {
                    Value = termNumber
                });

                var termInfo = new TermInfo
                {
                    RoundNumber = input.RoundNumber - 1,
                    TermNumber  = input.TermNumber - 1
                };
                Assert(SnapshotForTerm(termInfo).Success,
                       ContractErrorCode.GetErrorMessage(ContractErrorCode.AttemptFailed,
                                                         $"Failed to take snapshot of term {termInfo.TermNumber}"));
                Assert(SnapshotForMiners(termInfo).Success,
                       ContractErrorCode.GetErrorMessage(ContractErrorCode.AttemptFailed,
                                                         $"Failed to take snapshot of miners of term {termInfo.TermNumber}"));
                Assert(SendDividends(termInfo).Success,
                       ContractErrorCode.GetErrorMessage(ContractErrorCode.AttemptFailed,
                                                         $"Failed to send dividends of term {termInfo.TermNumber}"));
            }

            Context.LogDebug(() => $"Changing term number to {input.TermNumber}");
            TryToFindLIB();
            return(new Empty());
        }