private void OnBlockConnected(BlockConnected blockConnected) { ChainedHeaderBlock chBlock = blockConnected.ConnectedBlock; uint256 newFinalizedHash = this.finalizedBlockInfo.GetFinalizedBlockInfo().Hash; lock (this.locker) { foreach (Poll poll in this.polls.Where(x => !x.IsPending && x.PollVotedInFavorBlockData.Hash == newFinalizedHash).ToList()) { this.logger.LogDebug("Applying poll '{0}'.", poll); this.pollResultExecutor.ApplyChange(poll.VotingData); poll.PollExecutedBlockData = new HashHeightPair(chBlock.ChainedHeader); this.pollsRepository.UpdatePoll(poll); } } byte[] rawVotingData = this.votingDataEncoder.ExtractRawVotingData(chBlock.Block.Transactions[0]); if (rawVotingData == null) { this.logger.LogTrace("(-)[NO_VOTING_DATA]"); return; } // Pub key of a fed member that created voting data. string fedMemberKeyHex = this.slotsManager.GetFederationMemberForTimestamp(chBlock.Block.Header.Time).PubKey.ToHex(); List <VotingData> votingDataList = this.votingDataEncoder.Decode(rawVotingData); this.logger.LogDebug("Applying {0} voting data items included in a block by '{1}'.", votingDataList.Count, fedMemberKeyHex); lock (this.locker) { foreach (VotingData data in votingDataList) { Poll poll = this.polls.SingleOrDefault(x => x.VotingData == data && x.IsPending); if (poll == null) { poll = new Poll() { Id = this.pollsRepository.GetHighestPollId() + 1, PollVotedInFavorBlockData = null, PollExecutedBlockData = null, PollStartBlockData = new HashHeightPair(chBlock.ChainedHeader), VotingData = data, PubKeysHexVotedInFavor = new List <string>() { fedMemberKeyHex } }; this.polls.Add(poll); this.pollsRepository.AddPolls(poll); this.logger.LogDebug("New poll was created: '{0}'.", poll); } else if (!poll.PubKeysHexVotedInFavor.Contains(fedMemberKeyHex)) { poll.PubKeysHexVotedInFavor.Add(fedMemberKeyHex); this.pollsRepository.UpdatePoll(poll); this.logger.LogDebug("Voted on existing poll: '{0}'.", poll); } else { this.logger.LogDebug("Fed member '{0}' already voted for this poll. Ignoring his vote. Poll: '{1}'.", fedMemberKeyHex, poll); } List <string> fedMembersHex = this.federationManager.GetFederationMembers().Select(x => x.PubKey.ToHex()).ToList(); // It is possible that there is a vote from a federation member that was deleted from the federation. // Do not count votes from entities that are not active fed members. int validVotesCount = poll.PubKeysHexVotedInFavor.Count(x => fedMembersHex.Contains(x)); int requiredVotesCount = (fedMembersHex.Count / 2) + 1; this.logger.LogDebug("Fed members count: {0}, valid votes count: {1}, required votes count: {2}.", fedMembersHex.Count, validVotesCount, requiredVotesCount); if (validVotesCount < requiredVotesCount) { continue; } poll.PollVotedInFavorBlockData = new HashHeightPair(chBlock.ChainedHeader); this.pollsRepository.UpdatePoll(poll); } } }
private void OnBlockDisconnected(BlockDisconnected blockDisconnected) { ChainedHeaderBlock chBlock = blockDisconnected.DisconnectedBlock; lock (this.locker) { foreach (Poll poll in this.polls.Where(x => !x.IsPending && x.PollExecutedBlockData?.Hash == chBlock.ChainedHeader.HashBlock).ToList()) { this.logger.LogDebug("Reverting poll execution '{0}'.", poll); this.pollResultExecutor.RevertChange(poll.VotingData); poll.PollExecutedBlockData = null; this.pollsRepository.UpdatePoll(poll); } } byte[] rawVotingData = this.votingDataEncoder.ExtractRawVotingData(chBlock.Block.Transactions[0]); if (rawVotingData == null) { this.logger.LogTrace("(-)[NO_VOTING_DATA]"); return; } List <VotingData> votingDataList = this.votingDataEncoder.Decode(rawVotingData); votingDataList.Reverse(); lock (this.locker) { foreach (VotingData votingData in votingDataList) { if (this.IsVotingOnMultisigMember(votingData)) { continue; } // If the poll is pending, that's the one we want. There should be maximum 1 of these. Poll targetPoll = this.polls.SingleOrDefault(x => x.VotingData == votingData && x.IsPending); // Otherwise, get the most recent poll. There could currently be unlimited of these, though they're harmless. if (targetPoll == null) { targetPoll = this.polls.Last(x => x.VotingData == votingData); } this.logger.LogDebug("Reverting poll voting in favor: '{0}'.", targetPoll); if (targetPoll.PollVotedInFavorBlockData == new HashHeightPair(chBlock.ChainedHeader)) { targetPoll.PollVotedInFavorBlockData = null; this.pollsRepository.UpdatePoll(targetPoll); } // Pub key of a fed member that created voting data. string fedMemberKeyHex = this.slotsManager.GetFederationMemberForTimestamp(chBlock.Block.Header.Time).PubKey.ToHex(); targetPoll.PubKeysHexVotedInFavor.Remove(fedMemberKeyHex); if (targetPoll.PubKeysHexVotedInFavor.Count == 0) { this.polls.Remove(targetPoll); this.pollsRepository.RemovePolls(targetPoll.Id); this.logger.LogDebug("Poll with Id {0} was removed.", targetPoll.Id); } } } }
private void OnBlockConnected(BlockConnected blockConnected) { try { ChainedHeaderBlock chBlock = blockConnected.ConnectedBlock; uint256 newFinalizedHash = this.finalizedBlockInfo.GetFinalizedBlockInfo().Hash; lock (this.locker) { foreach (Poll poll in this.polls.Where(x => !x.IsPending && x.PollVotedInFavorBlockData.Hash == newFinalizedHash).ToList()) { this.logger.LogDebug("Applying poll '{0}'.", poll); this.pollResultExecutor.ApplyChange(poll.VotingData); poll.PollExecutedBlockData = new HashHeightPair(chBlock.ChainedHeader); this.pollsRepository.UpdatePoll(poll); } } byte[] rawVotingData = this.votingDataEncoder.ExtractRawVotingData(chBlock.Block.Transactions[0]); if (rawVotingData == null) { this.logger.LogTrace("(-)[NO_VOTING_DATA]"); return; } // Pub key of a fed member that created voting data. string fedMemberKeyHex = this.slotsManager.GetFederationMemberForTimestamp(chBlock.Block.Header.Time).PubKey.ToHex(); List <VotingData> votingDataList = this.votingDataEncoder.Decode(rawVotingData); this.logger.LogDebug("Applying {0} voting data items included in a block by '{1}'.", votingDataList.Count, fedMemberKeyHex); lock (this.locker) { foreach (VotingData data in votingDataList) { if (this.IsVotingOnMultisigMember(data)) { continue; } Poll poll = this.polls.SingleOrDefault(x => x.VotingData == data && x.IsPending); if (poll == null) { // Ensures that highestPollId can't be changed before the poll is committed. this.pollsRepository.Synchronous(() => { poll = new Poll() { Id = this.pollsRepository.GetHighestPollId() + 1, PollVotedInFavorBlockData = null, PollExecutedBlockData = null, PollStartBlockData = new HashHeightPair(chBlock.ChainedHeader), VotingData = data, PubKeysHexVotedInFavor = new List <string>() { fedMemberKeyHex } }; this.polls.Add(poll); this.pollsRepository.AddPolls(poll); this.logger.LogDebug("New poll was created: '{0}'.", poll); }); } else if (!poll.PubKeysHexVotedInFavor.Contains(fedMemberKeyHex)) { poll.PubKeysHexVotedInFavor.Add(fedMemberKeyHex); this.pollsRepository.UpdatePoll(poll); this.logger.LogDebug("Voted on existing poll: '{0}'.", poll); } else { this.logger.LogDebug("Fed member '{0}' already voted for this poll. Ignoring his vote. Poll: '{1}'.", fedMemberKeyHex, poll); } var fedMembersHex = new ConcurrentHashSet <string>(this.federationManager.GetFederationMembers().Select(x => x.PubKey.ToHex())); // Member that were about to be kicked when voting started don't participate. if (this.idleFederationMembersKicker != null) { ChainedHeader chainedHeader = chBlock.ChainedHeader.GetAncestor(poll.PollStartBlockData.Height); foreach (string pubKey in fedMembersHex) { if (this.idleFederationMembersKicker.ShouldMemberBeKicked(new PubKey(pubKey), chainedHeader.Header.Time, out _)) { fedMembersHex.TryRemove(pubKey); } } } // It is possible that there is a vote from a federation member that was deleted from the federation. // Do not count votes from entities that are not active fed members. int validVotesCount = poll.PubKeysHexVotedInFavor.Count(x => fedMembersHex.Contains(x)); int requiredVotesCount = (fedMembersHex.Count / 2) + 1; this.logger.LogDebug("Fed members count: {0}, valid votes count: {1}, required votes count: {2}.", fedMembersHex.Count, validVotesCount, requiredVotesCount); if (validVotesCount < requiredVotesCount) { continue; } poll.PollVotedInFavorBlockData = new HashHeightPair(chBlock.ChainedHeader); this.pollsRepository.UpdatePoll(poll); } } } catch (Exception ex) { this.logger.LogError(ex, ex.ToString()); throw; } }
public IActionResult GetCurrentMemberInfo() { try { if (this.federationManager.CurrentFederationKey == null) { throw new Exception("Your node is not registered as a federation member."); } var federationMemberModel = new FederationMemberDetailedModel { PubKey = this.federationManager.CurrentFederationKey.PubKey.ToHex() }; KeyValuePair <IFederationMember, uint> lastActive = this.federationHistory.GetFederationMembersByLastActiveTime().FirstOrDefault(x => x.Key.PubKey == this.federationManager.CurrentFederationKey.PubKey); if (lastActive.Key != null) { federationMemberModel.LastActiveTime = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(lastActive.Value); federationMemberModel.PeriodOfInActivity = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(lastActive.Value); } // Is this member part of a pending poll Poll poll = this.votingManager.GetPendingPolls().MemberPolls().OrderByDescending(p => p.PollStartBlockData.Height).FirstOrDefault(p => this.votingManager.GetMemberVotedOn(p.VotingData).PubKey == this.federationManager.CurrentFederationKey.PubKey); if (poll != null) { federationMemberModel.PollType = poll.VotingData.Key.ToString(); federationMemberModel.PollStartBlockHeight = poll.PollStartBlockData.Height; federationMemberModel.PollNumberOfVotesAcquired = poll.PubKeysHexVotedInFavor.Count; } // Has the poll finished? poll = this.votingManager.GetApprovedPolls().MemberPolls().OrderByDescending(p => p.PollVotedInFavorBlockData.Height).FirstOrDefault(p => this.votingManager.GetMemberVotedOn(p.VotingData).PubKey == this.federationManager.CurrentFederationKey.PubKey); if (poll != null) { federationMemberModel.PollType = poll.VotingData.Key.ToString(); federationMemberModel.PollStartBlockHeight = poll.PollStartBlockData.Height; federationMemberModel.PollNumberOfVotesAcquired = poll.PubKeysHexVotedInFavor.Count; federationMemberModel.PollFinishedBlockHeight = poll.PollVotedInFavorBlockData.Height; federationMemberModel.MemberWillStartMiningAtBlockHeight = poll.PollVotedInFavorBlockData.Height + this.network.Consensus.MaxReorgLength; federationMemberModel.MemberWillStartEarningRewardsEstimateHeight = federationMemberModel.MemberWillStartMiningAtBlockHeight + 480; if (this.chainIndexer.Height > poll.PollVotedInFavorBlockData.Height + this.network.Consensus.MaxReorgLength) { federationMemberModel.PollWillFinishInBlocks = 0; } else { federationMemberModel.PollWillFinishInBlocks = (poll.PollVotedInFavorBlockData.Height + this.network.Consensus.MaxReorgLength) - this.chainIndexer.Tip.Height; } } // Has the poll executed? poll = this.votingManager.GetExecutedPolls().MemberPolls().OrderByDescending(p => p.PollExecutedBlockData.Height).FirstOrDefault(p => this.votingManager.GetMemberVotedOn(p.VotingData).PubKey == this.federationManager.CurrentFederationKey.PubKey); if (poll != null) { federationMemberModel.PollExecutedBlockHeight = poll.PollExecutedBlockData.Height; } federationMemberModel.RewardEstimatePerBlock = 9d / this.federationManager.GetFederationMembers().Count; return(Json(federationMemberModel)); } catch (Exception e) { this.logger.Error("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
private void OnBlockConnected(BlockConnected blockConnected) { try { ChainedHeaderBlock chBlock = blockConnected.ConnectedBlock; HashHeightPair newFinalizedHash = this.finalizedBlockInfo.GetFinalizedBlockInfo(); lock (this.locker) { if (this.isBusyReconstructing) { foreach (Poll poll in this.GetApprovedPolls().ToList()) { if (blockConnected.ConnectedBlock.ChainedHeader.Height - poll.PollVotedInFavorBlockData.Height == this.network.Consensus.MaxReorgLength) { this.logger.LogDebug("Applying poll '{0}'.", poll); this.pollResultExecutor.ApplyChange(poll.VotingData); poll.PollExecutedBlockData = new HashHeightPair(chBlock.ChainedHeader); this.pollsRepository.UpdatePoll(poll); } } } else { foreach (Poll poll in this.GetApprovedPolls().Where(x => x.PollVotedInFavorBlockData.Hash == newFinalizedHash.Hash).ToList()) { this.logger.LogDebug("Applying poll '{0}'.", poll); this.pollResultExecutor.ApplyChange(poll.VotingData); poll.PollExecutedBlockData = new HashHeightPair(chBlock.ChainedHeader); this.pollsRepository.UpdatePoll(poll); } } } byte[] rawVotingData = this.votingDataEncoder.ExtractRawVotingData(chBlock.Block.Transactions[0]); if (rawVotingData == null) { this.logger.LogTrace("(-)[NO_VOTING_DATA]"); return; } string fedMemberKeyHex; // Please see the description under `VotingManagerV2ActivationHeight`. // PubKey of the federation member that created the voting data. if (this.poaConsensusOptions.VotingManagerV2ActivationHeight == 0 || blockConnected.ConnectedBlock.ChainedHeader.Height < this.poaConsensusOptions.VotingManagerV2ActivationHeight) { fedMemberKeyHex = this.federationHistory.GetFederationMemberForTimestamp(chBlock.Block.Header.Time, this.poaConsensusOptions).PubKey.ToHex(); } else { fedMemberKeyHex = this.federationHistory.GetFederationMemberForBlock(chBlock.ChainedHeader).PubKey.ToHex(); } List <VotingData> votingDataList = this.votingDataEncoder.Decode(rawVotingData); this.logger.LogDebug("Applying {0} voting data items included in a block by '{1}'.", votingDataList.Count, fedMemberKeyHex); lock (this.locker) { foreach (VotingData data in votingDataList) { if (this.federationManager.CurrentFederationKey?.PubKey.ToHex() == fedMemberKeyHex) { // Any votes found in the block is no longer scheduled. // This avoids clinging to votes scheduled during IBD. if (this.scheduledVotingData.Any(v => v == data)) { this.scheduledVotingData.Remove(data); } } if (this.IsVotingOnMultisigMember(data)) { continue; } Poll poll = this.polls.SingleOrDefault(x => x.VotingData == data && x.IsPending); if (poll == null) { // Ensures that highestPollId can't be changed before the poll is committed. this.pollsRepository.Synchronous(() => { poll = new Poll() { Id = this.pollsRepository.GetHighestPollId() + 1, PollVotedInFavorBlockData = null, PollExecutedBlockData = null, PollStartBlockData = new HashHeightPair(chBlock.ChainedHeader), VotingData = data, PubKeysHexVotedInFavor = new List <string>() { fedMemberKeyHex } }; this.polls.Add(poll); this.pollsRepository.AddPolls(poll); this.logger.LogDebug("New poll was created: '{0}'.", poll); }); } else if (!poll.PubKeysHexVotedInFavor.Contains(fedMemberKeyHex)) { poll.PubKeysHexVotedInFavor.Add(fedMemberKeyHex); this.pollsRepository.UpdatePoll(poll); this.logger.LogDebug("Voted on existing poll: '{0}'.", poll); } else { this.logger.LogDebug("Fed member '{0}' already voted for this poll. Ignoring his vote. Poll: '{1}'.", fedMemberKeyHex, poll); } var fedMembersHex = new ConcurrentHashSet <string>(this.federationManager.GetFederationMembers().Select(x => x.PubKey.ToHex())); // Member that were about to be kicked when voting started don't participate. if (this.idleFederationMembersKicker != null) { ChainedHeader chainedHeader = chBlock.ChainedHeader.GetAncestor(poll.PollStartBlockData.Height); if (chainedHeader?.Header == null) { this.logger.LogWarning("Couldn't retrieve header for block at height-hash: {0}-{1}.", poll.PollStartBlockData.Height, poll.PollStartBlockData.Hash?.ToString()); Guard.NotNull(chainedHeader, nameof(chainedHeader)); Guard.NotNull(chainedHeader.Header, nameof(chainedHeader.Header)); } foreach (string pubKey in fedMembersHex) { if (this.idleFederationMembersKicker.ShouldMemberBeKicked(new PubKey(pubKey), chainedHeader.Header.Time, out _)) { fedMembersHex.TryRemove(pubKey); } } } // It is possible that there is a vote from a federation member that was deleted from the federation. // Do not count votes from entities that are not active fed members. int validVotesCount = poll.PubKeysHexVotedInFavor.Count(x => fedMembersHex.Contains(x)); int requiredVotesCount = (fedMembersHex.Count / 2) + 1; this.logger.LogDebug("Fed members count: {0}, valid votes count: {1}, required votes count: {2}.", fedMembersHex.Count, validVotesCount, requiredVotesCount); if (validVotesCount < requiredVotesCount) { continue; } poll.PollVotedInFavorBlockData = new HashHeightPair(chBlock.ChainedHeader); this.pollsRepository.UpdatePoll(poll); } } } catch (Exception ex) { this.logger.LogError(ex, ex.ToString()); throw; } }
// TODO subscribe to signals // TODO more logs and log polls private void onBlockConnected(ChainedHeaderBlock chBlock) { byte[] rawVotingData = this.votingDataEncoder.ExtractRawVotingData(chBlock.Block.Transactions[0]); if (rawVotingData == null) { this.logger.LogTrace("(-)[NO_VOTING_DATA]"); return; } // Pub key of a fed member that created voting data. string fedMemberKeyHex = this.slotsManager.GetPubKeyForTimestamp(chBlock.Block.Header.Time).ToHex(); List <VotingData> votingDataList = this.votingDataEncoder.Decode(rawVotingData); lock (this.locker) { foreach (VotingData data in votingDataList) { Poll existingPoll = this.pendingPolls.SingleOrDefault(x => x.VotingData == data); if (existingPoll == null) { existingPoll = new Poll() { PollAppliedBlockHash = null, PollStartBlockHash = chBlock.Block.GetHash(), VotingData = data, PubKeysHexVotedInFavor = new List <string>() { fedMemberKeyHex } }; this.pendingPolls.Add(existingPoll); this.logger.LogDebug("New poll was created."); } else if (!existingPoll.PubKeysHexVotedInFavor.Contains(fedMemberKeyHex)) { existingPoll.PubKeysHexVotedInFavor.Add(fedMemberKeyHex); this.logger.LogDebug("Voted on existing poll."); } List <string> fedMembersHex = this.federationManager.GetFederationMembers().Select(x => x.ToHex()).ToList(); // It is possible that there is a vote from a federation member that was deleted from the federation. // Do not count votes from entities that are not active fed members. int validVotesCount = existingPoll.PubKeysHexVotedInFavor.Count(x => fedMembersHex.Contains(x)); int requiredVotesCount = (fedMembersHex.Count / 2) + 1; if (validVotesCount > requiredVotesCount) { // TODO apply changes, move active poll to finished polls, set finished block hash // TODO to apply changes use voting data result executor that has methods like apply change and revert change } } this.SavePolls(this.pendingPolls, PendingPollsDbKey); this.SavePolls(this.finishedPolls, FinishedPollsDbKey); } }