public void RemoveFederationMember(IFederationMember federationMember) { lock (this.locker) { this.federationMembers.Remove(federationMember); this.SaveFederation(this.federationMembers); this.SetIsFederationMember(); this.logger.LogInformation("Federation member '{0}' was removed!", federationMember); } this.signals.Publish(new FedMemberKicked(federationMember)); }
public override Task RunAsync(RuleContext context) { if (this.ibdState.IsInitialBlockDownload()) { this.Logger.LogTrace("(-)[SKIPPED_IN_IBD]"); return(Task.CompletedTask); } IFederationMember federationMember = this.slotsManager.GetFederationMemberForTimestamp(context.ValidationContext.BlockToValidate.Header.Time); byte[] rawCommitmentData = this.encoder.ExtractRawCommitmentData(context.ValidationContext.BlockToValidate.Transactions.First()); if (rawCommitmentData == null) { // Every PoA miner on sidechain network is enforced to include commitment data to the blocks mined. // Not having a commitment always should result in a permanent ban of the block. this.Logger.LogTrace("(-)[NO_COMMITMENT_FOUND]"); PoAConsensusErrors.InvalidCollateralAmount.Throw(); } int commitmentHeight = this.encoder.Decode(rawCommitmentData); this.Logger.LogDebug("Commitment is: {0}.", commitmentHeight); int counterChainHeight = this.collateralChecker.GetCounterChainConsensusHeight(); int maxReorgLength = AddressIndexer.GetMaxReorgOrFallbackMaxReorg(this.network); // Check if commitment height is less than `mainchain consensus tip height - MaxReorg`. if (commitmentHeight > counterChainHeight - maxReorgLength) { // Temporary reject the block since it's possible that due to network connectivity problem counter chain is out of sync and // we are relying on chain state old data. It is possible that when we advance on counter chain commitment height will be // sufficiently old. context.ValidationContext.RejectUntil = this.dateTime.GetUtcNow() + TimeSpan.FromSeconds(this.collateralCheckBanDurationSeconds); this.Logger.LogTrace("(-)[COMMITMENT_TOO_NEW]"); PoAConsensusErrors.InvalidCollateralAmount.Throw(); } if (!this.collateralChecker.CheckCollateral(federationMember, commitmentHeight)) { // By setting rejectUntil we avoid banning a peer that provided a block. context.ValidationContext.RejectUntil = this.dateTime.GetUtcNow() + TimeSpan.FromSeconds(this.collateralCheckBanDurationSeconds); this.Logger.LogTrace("(-)[BAD_COLLATERAL]"); PoAConsensusErrors.InvalidCollateralAmount.Throw(); } return(Task.CompletedTask); }
/// <inheritdoc /> public bool CheckCollateral(IFederationMember federationMember, int heightToCheckAt) { if (!this.collateralUpdated) { this.logger.LogTrace("(-)[NOT_INITIALIZED]"); throw new Exception("Component is not initialized!"); } var member = federationMember as CollateralFederationMember; if (member == null) { this.logger.LogTrace("(-)[WRONG_TYPE]"); throw new ArgumentException($"{nameof(federationMember)} should be of type: {nameof(CollateralFederationMember)}."); } lock (this.locker) { if (heightToCheckAt > this.counterChainConsensusTipHeight - this.maxReorgLength) { this.logger.LogTrace("(-)[INVALID_CHECK_HEIGHT]:false"); return(false); } } if ((member.CollateralAmount == null) || (member.CollateralAmount == 0)) { this.logger.LogTrace("(-)[NO_COLLATERAL_REQUIREMENT]:true"); return(true); } lock (this.locker) { AddressIndexerData balanceData = this.balancesDataByAddress[member.CollateralMainchainAddress]; if (balanceData == null) { // No data. Assume collateral is 0. It's ok if there is no collateral set for that fed member. this.logger.LogTrace("(-)[NO_DATA]"); return(0 >= member.CollateralAmount); } long balance = balanceData.BalanceChanges.Where(x => x.BalanceChangedHeight <= heightToCheckAt).CalculateBalance(); this.logger.LogDebug("Calculated balance at {0} is {1}, collateral requirement is {2}.", heightToCheckAt, balance, member.CollateralAmount); return(balance >= member.CollateralAmount.Satoshi); } }
/// <inheritdoc /> public bool CheckCollateral(IFederationMember federationMember, int heightToCheckAt) { if (!this.collateralUpdated) { this.logger.Debug("(-)[NOT_INITIALIZED]"); throw new Exception("Component is not initialized!"); } var member = federationMember as CollateralFederationMember; if (member == null) { this.logger.Debug("(-)[WRONG_TYPE]"); throw new ArgumentException($"{nameof(federationMember)} should be of type: {nameof(CollateralFederationMember)}."); } lock (this.locker) { if (heightToCheckAt > this.counterChainConsensusTipHeight - this.maxReorgLength) { this.logger.Debug("(-)[HEIGHTTOCHECK_HIGHER_THAN_COUNTER_TIP_LESS_MAXREORG]:{0}={1}, {2}={3}:false", nameof(heightToCheckAt), heightToCheckAt, nameof(this.counterChainConsensusTipHeight), this.counterChainConsensusTipHeight); return(false); } } if ((member.CollateralAmount == null) || (member.CollateralAmount == 0)) { this.logger.Debug("(-)[NO_COLLATERAL_REQUIREMENT]:true"); return(true); } lock (this.locker) { AddressIndexerData balanceData = this.balancesDataByAddress[member.CollateralMainchainAddress]; if (balanceData == null) { // No data. Assume collateral is 0. It's ok if there is no collateral set for that fed member. this.logger.Debug("(-)[NO_DATA]:{0}={1}", nameof(this.balancesDataByAddress.Count), this.balancesDataByAddress.Count); return(0 >= member.CollateralAmount); } long balance = balanceData.BalanceChanges.Where(x => x.BalanceChangedHeight <= heightToCheckAt).CalculateBalance(); this.logger.Info("Calculated balance for '{0}' at {1} is {2}, collateral requirement is {3}.", member.CollateralMainchainAddress, heightToCheckAt, Money.Satoshis(balance).ToUnit(MoneyUnit.BTC), member.CollateralAmount); return(balance >= member.CollateralAmount.Satoshi); } }
/// <remarks>Should be protected by <see cref="locker"/>.</remarks> protected virtual void AddFederationMemberLocked(IFederationMember federationMember) { if (this.federationMembers.Contains(federationMember)) { this.logger.LogTrace("(-)[ALREADY_EXISTS]"); return; } this.federationMembers.Add(federationMember); this.SaveFederation(this.federationMembers); this.SetIsFederationMember(); this.logger.LogInformation("Federation member '{0}' was added!", federationMember); }
/// <summary> /// It is possible that this node was not a federation member at the time a pending poll was started. /// As such the node would not have voted up to now. We have to check if a vote should be added now. /// </summary> /// <remarks> /// There is another scenario catered for by this method. It's the situation where a node crashed or is /// stopped when it contains scheduled "add member" votes that have not yet been added to a block. /// </remarks> private void OnBeforeFillBlockTemplate() { if (!this.federationManager.IsFederationMember || !this.network.ConsensusOptions.VotingEnabled) { return; } try { List <Poll> pendingAddFederationMemberPolls = this.votingManager.GetPendingPolls().Where(p => p.VotingData.Key == VoteKey.AddFederationMember).ToList(); // Filter all polls where this federation number has not voted on. pendingAddFederationMemberPolls = pendingAddFederationMemberPolls.Where(p => !p.PubKeysHexVotedInFavor.Contains(this.federationManager.CurrentFederationKey.PubKey.ToString())).ToList(); if (!pendingAddFederationMemberPolls.Any()) { return; } IFederationMember collateralFederationMember = this.federationManager.GetCurrentFederationMember(); var poaConsensusFactory = this.network.Consensus.ConsensusFactory as PoAConsensusFactory; foreach (Poll poll in pendingAddFederationMemberPolls) { ChainedHeader pollStartHeader = this.chainIndexer.GetHeader(poll.PollStartBlockData.Hash); ChainedHeader votingRequestHeader = pollStartHeader.Previous; // Already checked? if (this.joinFederationRequestMonitor.AlreadyChecked(votingRequestHeader.HashBlock)) { continue; } var blockData = this.consensusManager.GetBlockData(votingRequestHeader.HashBlock); this.joinFederationRequestMonitor.OnBlockConnected(new Bitcoin.EventBus.CoreEvents.BlockConnected( new ChainedHeaderBlock(blockData.Block, votingRequestHeader))); } return; } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return; } }
/// <inheritdoc /> public void Execute(ChainedHeader consensusTip) { // No member can be kicked at genesis. if (consensusTip.Height == 0) { return; } try { PubKey pubKey = this.slotsManager.GetFederationMemberForBlock(consensusTip, this.votingManager).PubKey; this.fedPubKeysByLastActiveTime.AddOrReplace(pubKey, consensusTip.Header.Time); this.SaveMembersByLastActiveTime(); // Check if any fed member was idle for too long. Use the timestamp of the mined block. foreach (KeyValuePair <PubKey, uint> fedMemberToActiveTime in this.fedPubKeysByLastActiveTime) { if (this.ShouldMemberBeKicked(fedMemberToActiveTime.Key, consensusTip.Header.Time, out uint inactiveForSeconds)) { IFederationMember memberToKick = this.federationManager.GetFederationMembers().SingleOrDefault(x => x.PubKey == fedMemberToActiveTime.Key); byte[] federationMemberBytes = this.consensusFactory.SerializeFederationMember(memberToKick); bool alreadyKicking = this.votingManager.AlreadyVotingFor(VoteKey.KickFederationMember, federationMemberBytes); if (!alreadyKicking) { this.logger.LogWarning("Federation member '{0}' was inactive for {1} seconds and will be scheduled to be kicked.", fedMemberToActiveTime.Key, inactiveForSeconds); this.votingManager.ScheduleVote(new VotingData() { Key = VoteKey.KickFederationMember, Data = federationMemberBytes }); } else { this.logger.LogDebug("Skipping because kicking is already voted for."); } } } } catch (Exception ex) { this.logger.LogError(ex, ex.ToString()); throw; } }
/// <inheritdoc /> protected override void FillBlockTemplate(BlockTemplate blockTemplate, out bool dropTemplate) { OnBeforeFillBlockTemplate(); base.FillBlockTemplate(blockTemplate, out dropTemplate); int counterChainHeight = this.collateralChecker.GetCounterChainConsensusHeight(); int maxReorgLength = AddressIndexer.GetMaxReorgOrFallbackMaxReorg(this.network); int commitmentHeight = counterChainHeight - maxReorgLength - AddressIndexer.SyncBuffer; if (commitmentHeight <= 0) { dropTemplate = true; this.logger.LogWarning("Counter chain should first advance at least at {0}! Block can't be produced.", maxReorgLength + AddressIndexer.SyncBuffer); this.logger.LogTrace("(-)[LOW_COMMITMENT_HEIGHT]"); return; } IFederationMember currentMember = this.federationManager.GetCurrentFederationMember(); if (currentMember == null) { dropTemplate = true; this.logger.LogWarning("Unable to get this node's federation member!"); this.logger.LogTrace("(-)[CANT_GET_FED_MEMBER]"); return; } // Check our own collateral at a given commitment height. bool success = this.collateralChecker.CheckCollateral(currentMember, commitmentHeight); if (!success) { dropTemplate = true; this.logger.LogWarning("Failed to fulfill collateral requirement for mining!"); this.logger.LogTrace("(-)[BAD_COLLATERAL]"); return; } // Add height commitment. byte[] encodedHeight = this.encoder.EncodeCommitmentHeight(commitmentHeight); var heightCommitmentScript = new Script(OpcodeType.OP_RETURN, Op.GetPushOp(encodedHeight), Op.GetPushOp(this.counterChainNetwork.MagicBytes)); blockTemplate.Block.Transactions[0].AddOutput(Money.Zero, heightCommitmentScript); }
private void OnBlockConnected(BlockConnected blockConnectedData) { try { // Update last active time. uint timestamp = blockConnectedData.ConnectedBlock.ChainedHeader.Header.Time; PubKey key = this.slotsManager.GetFederationMemberForBlock(blockConnectedData.ConnectedBlock.ChainedHeader, this.votingManager).PubKey; this.fedPubKeysByLastActiveTime.AddOrReplace(key, timestamp); this.SaveMembersByLastActiveTime(); // Check if any fed member was idle for too long. foreach (KeyValuePair <PubKey, uint> fedMemberToActiveTime in this.fedPubKeysByLastActiveTime) { // Check if any fed member was idle for too long. Use the timestamp of the connecting block. if (this.ShouldBeKicked(fedMemberToActiveTime.Key, timestamp, out uint inactiveForSeconds)) { IFederationMember memberToKick = this.federationManager.GetFederationMembers().SingleOrDefault(x => x.PubKey == fedMemberToActiveTime.Key); byte[] federationMemberBytes = this.consensusFactory.SerializeFederationMember(memberToKick); bool alreadyKicking = this.votingManager.AlreadyVotingFor(VoteKey.KickFederationMember, federationMemberBytes); if (!alreadyKicking) { this.logger.LogWarning("Federation member '{0}' was inactive for {1} seconds and will be scheduled to be kicked.", fedMemberToActiveTime.Key, inactiveForSeconds); this.votingManager.ScheduleVote(new VotingData() { Key = VoteKey.KickFederationMember, Data = federationMemberBytes }); } else { this.logger.LogDebug("Skipping because kicking is already voted for."); } } } } catch (Exception ex) { this.logger.LogError(ex, ex.ToString()); throw; } }
private bool IsVotingOnMultisigMember(VotingData votingData) { if (votingData.Key != VoteKey.AddFederationMember && votingData.Key != VoteKey.KickFederationMember) { return(false); } if (!(this.network.Consensus.ConsensusFactory is PoAConsensusFactory poaConsensusFactory)) { return(false); } IFederationMember member = poaConsensusFactory.DeserializeFederationMember(votingData.Data); // Ignore votes on multisig-members. return(FederationVotingController.IsMultisigMember(this.network, member.PubKey)); }
public IActionResult VoteKickFederationMember([FromBody] KickFederationMemberModel model) { Guard.NotNull(model, nameof(model)); if (!this.ModelState.IsValid) { return(ModelStateErrors.BuildErrorResponse(this.ModelState)); } if (!this.federationManager.IsFederationMember) { return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Only federation members can vote.", string.Empty)); } try { IFederationMember federationMember = this.federationManager.GetFederationMembers().SingleOrDefault(m => m.PubKey.ToHex() == model.PubKey); if (federationMember == null) { return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, $"'{model.PubKey}' is not currently a federation member.", string.Empty)); } var consensusFactory = this.network.Consensus.ConsensusFactory as PoAConsensusFactory; byte[] federationMemberBytes = consensusFactory.SerializeFederationMember(federationMember); bool alreadyKicking = this.votingManager.AlreadyVotingFor(VoteKey.KickFederationMember, federationMemberBytes); if (alreadyKicking) { return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, $"Skipping because kicking {model.PubKey} is already being voted on.", string.Empty)); } this.votingManager.ScheduleVote(new VotingData() { Key = VoteKey.KickFederationMember, Data = federationMemberBytes }); return(Ok($"A vote to kick '{model.PubKey}' has now been scheduled.")); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "There was a problem executing a vote to be scheduled.", e.ToString())); } }
public override Task RunAsync(RuleContext context) { if (this.ibdState.IsInitialBlockDownload()) { this.Logger.LogTrace("(-)[SKIPPED_IN_IBD]"); return(Task.CompletedTask); } IFederationMember federationMember = this.slotsManager.GetFederationMemberForTimestamp(context.ValidationContext.BlockToValidate.Header.Time); if (!this.collateralChecker.CheckCollateral(federationMember)) { this.Logger.LogTrace("(-)[BAD_COLLATERAL]"); PoAConsensusErrors.InvalidCollateralAmount.Throw(); } return(Task.CompletedTask); }
private void OnBlockConnected(BlockConnected blockConnectedData) { // Update last active time. uint timestamp = blockConnectedData.ConnectedBlock.ChainedHeader.Header.Time; PubKey key = this.slotsManager.GetFederationMemberForTimestamp(timestamp).PubKey; this.fedPubKeysByLastActiveTime.AddOrReplace(key, timestamp); this.SaveMembersByLastActiveTime(); // Check if any fed member was idle for too long. ChainedHeader tip = this.consensusManager.Tip; foreach (KeyValuePair <PubKey, uint> fedMemberToActiveTime in this.fedPubKeysByLastActiveTime) { uint inactiveForSeconds = tip.Header.Time - fedMemberToActiveTime.Value; if (inactiveForSeconds > this.federationMemberMaxIdleTimeSeconds && this.federationManager.IsFederationMember && !FederationVotingController.IsMultisigMember(this.network, fedMemberToActiveTime.Key)) { IFederationMember memberToKick = this.federationManager.GetFederationMembers().SingleOrDefault(x => x.PubKey == fedMemberToActiveTime.Key); byte[] federationMemberBytes = this.consensusFactory.SerializeFederationMember(memberToKick); bool alreadyKicking = this.votingManager.AlreadyVotingFor(VoteKey.KickFederationMember, federationMemberBytes); if (!alreadyKicking) { this.logger.LogWarning("Federation member '{0}' was inactive for {1} seconds and will be scheduled to be kicked.", fedMemberToActiveTime.Key, inactiveForSeconds); this.votingManager.ScheduleVote(new VotingData() { Key = VoteKey.KickFederationMember, Data = federationMemberBytes }); } else { this.logger.LogDebug("Skipping because kicking is already voted for."); } } } }
/// <inheritdoc /> public string ConvertToString(VotingData data) { string action = $"Action:'{data.Key}'"; switch (data.Key) { case VoteKey.AddFederationMember: case VoteKey.KickFederationMember: IFederationMember federationMember = this.consensusFactory.DeserializeFederationMember(data.Data); return($"{action},FederationMember:'{federationMember}'"); case VoteKey.WhitelistHash: case VoteKey.RemoveHash: var hash = new uint256(data.Data); return($"{action},Hash:'{hash}'"); } return("unknown (not supported voting data key)"); }
public virtual void AddFederationMember(IFederationMember federationMember) { lock (this.locker) { if (this.federationMembers.Contains(federationMember)) { this.logger.LogTrace("(-)[ALREADY_EXISTS]"); return; } this.federationMembers.Add(federationMember); this.SaveFederation(this.federationMembers); this.SetIsFederationMember(); this.logger.LogInformation("Federation member '{0}' was added!", federationMember); } this.signals.Publish(new FedMemberAdded(federationMember)); }
public bool CheckCollateral(IFederationMember federationMember) { var member = federationMember as CollateralFederationMember; if (member == null) { this.logger.LogTrace("(-)[WRONG_TYPE]"); throw new ArgumentException($"{nameof(federationMember)} should be of type: {nameof(CollateralFederationMember)}."); } if ((member.CollateralAmount == null) || (member.CollateralAmount == 0)) { this.logger.LogTrace("(-)[NO_COLLATERAL_REQUIREMENT]:true"); return(true); } lock (this.locker) { return(this.depositsByAddress[member.CollateralMainchainAddress] >= member.CollateralAmount); } }
public override byte[] SerializeFederationMember(IFederationMember federationMember) { var member = federationMember as CollateralFederationMember; if (member == null) { throw new ArgumentException($"Member of type: '{nameof(CollateralFederationMember)}' should be provided."); } var model = new CollateralFederationMemberModel() { CollateralMainchainAddress = member.CollateralMainchainAddress, CollateralAmountSatoshis = member.CollateralAmount, PubKeyHex = member.PubKey.ToHex() }; string json = Serializer.ToString(model); byte[] jsonBytes = Encoding.ASCII.GetBytes(json); return(jsonBytes); }
private IFederationMember DetermineExpectedMinerForTimestamp(uint headerTime) { // When blocks are missing for given timestamps we have to determine whom was supposed to mine // by looking at the federation make-up at the time and whom mined last. ChainedHeader prevBlockMined = this.consensusManager.Tip; while (prevBlockMined.Header.Time > headerTime) { prevBlockMined = prevBlockMined.Previous; } IFederationMember minerForBlock = this.federationHistory.GetFederationMemberForBlock(prevBlockMined); List <IFederationMember> federationAtBlock = this.federationHistory.GetFederationForBlock(prevBlockMined); int offset = (int)((headerTime - prevBlockMined.Header.Time) / this.network.ConsensusOptions.TargetSpacingSeconds); int minerForBlockIndex = federationAtBlock.TakeWhile(m => m.PubKey != minerForBlock.PubKey).Count(); int expectedIndex = (minerForBlockIndex + offset) % federationAtBlock.Count; return(federationAtBlock[expectedIndex]); }
/// <summary>Adds a federation member to the internal list of federation members.</summary> /// <param name="federationMember">The <see cref="IFederationMember"/> to add.</param> /// <remarks>Should be protected by <see cref="locker"/>.</remarks> private void AddFederationMemberLocked(IFederationMember federationMember) { if (federationMember is CollateralFederationMember collateralFederationMember) { if (this.federationMembers.IsCollateralAddressRegistered(collateralFederationMember.CollateralMainchainAddress)) { this.logger.LogWarning($"Federation member with address '{collateralFederationMember.CollateralMainchainAddress}' already exists."); return; } if (this.federationMembers.Contains(federationMember)) { this.logger.LogTrace("(-)[ALREADY_EXISTS]"); return; } } this.federationMembers.Add(federationMember); this.SetIsFederationMember(); this.logger.LogInformation("Federation member '{0}' was added.", federationMember); }
/// <summary>Should be protected by <see cref="locker"/>.</summary> private void AddFederationMemberLocked(IFederationMember federationMember) { if (federationMember is CollateralFederationMember collateralFederationMember) { if (this.federationMembers.Cast <CollateralFederationMember>().Any(x => x.CollateralMainchainAddress == collateralFederationMember.CollateralMainchainAddress)) { this.logger.LogTrace("(-)[DUPLICATED_COLLATERAL_ADDR]"); return; } if (this.federationMembers.Contains(federationMember)) { this.logger.LogTrace("(-)[ALREADY_EXISTS]"); return; } } this.federationMembers.Add(federationMember); this.SetIsFederationMember(); this.logger.LogInformation("Federation member '{0}' was added.", federationMember); }
public List <IFederationMember> GetFederationFromExecutedPolls() { lock (this.locker) { var federation = new List <IFederationMember>(this.poaConsensusOptions.GenesisFederationMembers); IEnumerable <Poll> executedPolls = this.GetExecutedPolls().MemberPolls(); foreach (Poll poll in executedPolls.OrderBy(a => a.PollExecutedBlockData.Height)) { IFederationMember federationMember = ((PoAConsensusFactory)(this.network.Consensus.ConsensusFactory)).DeserializeFederationMember(poll.VotingData.Data); if (poll.VotingData.Key == VoteKey.AddFederationMember) { federation.Add(federationMember); } else if (poll.VotingData.Key == VoteKey.KickFederationMember) { federation.Remove(federationMember); } } return(federation); } }
private void AddComponentStats(StringBuilder log) { log.AppendLine(); log.AppendLine(">> Voting & Poll Data"); lock (this.locker) { double avgBlockProcessingTime; if (this.blocksProcessed == 0) { avgBlockProcessingTime = double.NaN; } else { avgBlockProcessingTime = Math.Round((double)(new TimeSpan(this.blocksProcessingTime).Milliseconds) / this.blocksProcessed, 2); } double avgBlockProcessingThroughput = Math.Round(this.blocksProcessed / (new TimeSpan(this.blocksProcessingTime).TotalSeconds), 2); log.AppendLine("Polls Repository Height".PadRight(LoggingConfiguration.ColumnLength) + $": {(this.PollsRepository.CurrentTip?.Height ?? 0)}".PadRight(10) + $"(Hash: {(this.PollsRepository.CurrentTip?.Hash.ToString())})"); log.AppendLine("Blocks Processed".PadRight(LoggingConfiguration.ColumnLength) + $": {this.blocksProcessed}".PadRight(18) + $"Avg Time: { avgBlockProcessingTime } ms".PadRight(20) + $"Throughput: { avgBlockProcessingThroughput } per second"); log.AppendLine(); log.AppendLine( "Expired Member Polls".PadRight(24) + ": " + GetExpiredPolls().MemberPolls().Count.ToString().PadRight(16) + "Expired Whitelist Polls".PadRight(30) + ": " + GetExpiredPolls().WhitelistPolls().Count); log.AppendLine( "Pending Member Polls".PadRight(24) + ": " + GetPendingPolls().MemberPolls().Count.ToString().PadRight(16) + "Pending Whitelist Polls".PadRight(30) + ": " + GetPendingPolls().WhitelistPolls().Count); log.AppendLine( "Approved Member Polls".PadRight(24) + ": " + GetApprovedPolls().MemberPolls().Where(x => !x.IsExecuted).Count().ToString().PadRight(16) + "Approved Whitelist Polls".PadRight(30) + ": " + GetApprovedPolls().WhitelistPolls().Where(x => !x.IsExecuted).Count()); log.AppendLine( "Executed Member Polls".PadRight(24) + ": " + GetExecutedPolls().MemberPolls().Count.ToString().PadRight(16) + "Executed Whitelist Polls".PadRight(30) + ": " + GetExecutedPolls().WhitelistPolls().Count); log.AppendLine( "Scheduled Votes".PadRight(24) + ": " + this.scheduledVotingData.Count.ToString().PadRight(16) + "Scheduled votes will be added to the next block this node mines."); if (this.nodeStats.DisplayBenchStats) { long tipHeight = this.chainIndexer.Tip.Height; List <Poll> pendingPolls = GetPendingPolls().OrderByDescending(p => p.PollStartBlockData.Height).ToList(); if (pendingPolls.Count != 0) { log.AppendLine(); log.AppendLine("--- Pending Add/Kick Member Polls ---"); foreach (Poll poll in pendingPolls.Where(p => !p.IsExecuted && (p.VotingData.Key == VoteKey.AddFederationMember || p.VotingData.Key == VoteKey.KickFederationMember))) { IFederationMember federationMember = ((PoAConsensusFactory)(this.network.Consensus.ConsensusFactory)).DeserializeFederationMember(poll.VotingData.Data); string expiresIn = $", Expires In = {(Math.Max(this.poaConsensusOptions.Release1100ActivationHeight, poll.PollStartBlockData.Height + this.poaConsensusOptions.PollExpiryBlocks) - tipHeight)}"; log.Append($"{poll.VotingData.Key.ToString().PadLeft(22)}, PubKey = { federationMember.PubKey.ToHex() }, In Favor = {poll.PubKeysHexVotedInFavor.Count}{expiresIn}"); bool exists = this.federationManager.GetFederationMembers().Any(m => m.PubKey == federationMember.PubKey); if (poll.VotingData.Key == VoteKey.AddFederationMember && exists) { log.Append(" (Already exists)"); } if (poll.VotingData.Key == VoteKey.KickFederationMember && !exists) { log.Append(" (Does not exist)"); } log.AppendLine(); } } List <Poll> approvedPolls = GetApprovedPolls().Where(p => !p.IsExecuted).OrderByDescending(p => p.PollVotedInFavorBlockData.Height).ToList(); if (approvedPolls.Count != 0) { log.AppendLine(); log.AppendLine("--- Approved Polls ---"); foreach (Poll poll in approvedPolls) { log.AppendLine($"{poll.VotingData.Key.ToString().PadLeft(22)}, Applied In = ({(poll.PollStartBlockData.Height - (tipHeight - this.network.Consensus.MaxReorgLength))})"); } } } } log.AppendLine(); }
private void ProcessBlock(DBreeze.Transactions.Transaction transaction, ChainedHeaderBlock chBlock) { long flagFall = DateTime.Now.Ticks; try { lock (this.locker) { bool pollsRepositoryModified = false; foreach (Poll poll in this.GetPendingPolls().Where(p => PollsRepository.IsPollExpiredAt(p, chBlock.ChainedHeader, this.network as PoANetwork)).ToList()) { this.logger.LogDebug("Expiring poll '{0}'.", poll); // Flag the poll as expired. The "PollVotedInFavorBlockData" will always be null at this point due to the "GetPendingPolls" filter above. // The value of the hash is not significant but we set it to a non-zero value to prevent the field from being de-serialized as null. poll.IsExpired = true; this.polls.OnPendingStatusChanged(poll); this.PollsRepository.UpdatePoll(transaction, poll); pollsRepositoryModified = true; } foreach (Poll poll in this.GetApprovedPolls()) { if (poll.IsExpired || chBlock.ChainedHeader.Height != (poll.PollVotedInFavorBlockData.Height + this.network.Consensus.MaxReorgLength)) { continue; } this.logger.LogDebug("Applying poll '{0}'.", poll); this.pollResultExecutor.ApplyChange(poll.VotingData); poll.PollExecutedBlockData = new HashHeightPair(chBlock.ChainedHeader); this.PollsRepository.UpdatePoll(transaction, poll); pollsRepositoryModified = true; } if (this.federationManager.GetMultisigMinersApplicabilityHeight() == chBlock.ChainedHeader.Height) { this.federationManager.UpdateMultisigMiners(true); } byte[] rawVotingData = this.votingDataEncoder.ExtractRawVotingData(chBlock.Block.Transactions[0]); if (rawVotingData == null) { this.PollsRepository.SaveCurrentTip(pollsRepositoryModified ? transaction : null, chBlock.ChainedHeader); return; } IFederationMember member = this.federationHistory.GetFederationMemberForBlock(chBlock.ChainedHeader); if (member == null) { this.logger.LogError("The block was mined by a non-federation-member!"); this.logger.LogTrace("(-)[ALIEN_BLOCK]"); this.PollsRepository.SaveCurrentTip(pollsRepositoryModified ? transaction : null, chBlock.ChainedHeader); return; } PubKey fedMemberKey = member.PubKey; string fedMemberKeyHex = fedMemberKey.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.GetPendingPollByVotingData(data); 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 <Vote>() { new Vote() { PubKey = fedMemberKeyHex, Height = chBlock.ChainedHeader.Height } } }; this.polls.Add(poll); this.PollsRepository.AddPolls(transaction, poll); pollsRepositoryModified = true; this.logger.LogDebug("New poll was created: '{0}'.", poll); }); } else if (!poll.PubKeysHexVotedInFavor.Any(v => v.PubKey == fedMemberKeyHex)) { poll.PubKeysHexVotedInFavor.Add(new Vote() { PubKey = fedMemberKeyHex, Height = chBlock.ChainedHeader.Height }); this.PollsRepository.UpdatePoll(transaction, poll); pollsRepositoryModified = true; 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 <IFederationMember> modifiedFederation = this.federationManager.GetFederationMembers(); var fedMembersHex = new ConcurrentHashSet <string>(modifiedFederation.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) { chainedHeader = this.chainIndexer.GetHeader(poll.PollStartBlockData.Hash); if (chainedHeader == 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 (IFederationMember miner in modifiedFederation) { if (this.idleFederationMembersKicker.ShouldMemberBeKicked(miner, chainedHeader, chBlock.ChainedHeader, out _)) { fedMembersHex.TryRemove(miner.PubKey.ToHex()); } } } // 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.PubKey)); 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.polls.OnPendingStatusChanged(poll); this.PollsRepository.UpdatePoll(transaction, poll); pollsRepositoryModified = true; } } this.PollsRepository.SaveCurrentTip(pollsRepositoryModified ? transaction : null, chBlock.ChainedHeader); } } catch (Exception ex) { this.logger.LogError(ex, ex.ToString()); throw; } finally { long timeConsumed = DateTime.Now.Ticks - flagFall; this.blocksProcessed++; this.blocksProcessingTime += timeConsumed; } }
public IEnumerable <(List <IFederationMember> federation, HashSet <IFederationMember> whoJoined)> GetFederationsForHeights(int startHeight, int endHeight) { lock (this.locker) { // Starting with the genesis federation... List <IFederationMember> modifiedFederation = new List <IFederationMember>(this.poaConsensusOptions.GenesisFederationMembers); Poll[] approvedPolls = this.GetApprovedPolls().MemberPolls().OrderBy(a => a.PollVotedInFavorBlockData.Height).ToArray(); int pollIndex = 0; bool straxEra = false; int? multisigMinersApplicabilityHeight = this.federationManager.GetMultisigMinersApplicabilityHeight(); for (int height = startHeight; height <= endHeight; height++) { var whoJoined = new HashSet <IFederationMember>(); if (!(this.network.Consensus.ConsensusFactory is PoAConsensusFactory poaConsensusFactory)) { yield return(new List <IFederationMember>(this.poaConsensusOptions.GenesisFederationMembers), new HashSet <IFederationMember>((height != 0) ? new List <IFederationMember>() : this.poaConsensusOptions.GenesisFederationMembers)); continue; } if (!straxEra && (multisigMinersApplicabilityHeight != null && height >= multisigMinersApplicabilityHeight)) { EnterStraxEra(modifiedFederation); straxEra = true; } // Apply all polls that executed at or before the current height. for (; pollIndex < approvedPolls.Length; pollIndex++) { // Modify the federation with the polls that would have been executed up to the given height. Poll poll = approvedPolls[pollIndex]; // If it executed after the current height then exit this loop. int pollExecutionHeight = poll.PollVotedInFavorBlockData.Height + (int)this.network.Consensus.MaxReorgLength; if (pollExecutionHeight > height) { break; } IFederationMember federationMember = ((PoAConsensusFactory)(this.network.Consensus.ConsensusFactory)).DeserializeFederationMember(poll.VotingData.Data); // Addition/removal. if (poll.VotingData.Key == VoteKey.AddFederationMember) { if (!modifiedFederation.Contains(federationMember)) { if (straxEra && federationMember is CollateralFederationMember collateralFederationMember) { if (modifiedFederation.IsCollateralAddressRegistered(collateralFederationMember.CollateralMainchainAddress)) { this.logger.LogDebug("Not adding member '{0}' with duplicate collateral address '{1}'.", collateralFederationMember.PubKey.ToHex(), collateralFederationMember.CollateralMainchainAddress); continue; } bool shouldBeMultisigMember = ((PoANetwork)this.network).StraxMiningMultisigMembers.Contains(federationMember.PubKey); if (collateralFederationMember.IsMultisigMember != shouldBeMultisigMember) { collateralFederationMember.IsMultisigMember = shouldBeMultisigMember; } } if (pollExecutionHeight == height) { whoJoined.Add(federationMember); } modifiedFederation.Add(federationMember); } } else if (poll.VotingData.Key == VoteKey.KickFederationMember) { if (modifiedFederation.Contains(federationMember)) { modifiedFederation.Remove(federationMember); } } } yield return(new List <IFederationMember>(modifiedFederation), whoJoined); } } }
public FedMemberAdded(IFederationMember addedMember) { this.AddedMember = addedMember; }
public uint GetMiningTimestamp(uint currentTime) { /* * A miner can calculate when its expected to mine by looking at the ordered list of federation members * and the last block that was mined and by whom. It can count the number of mining slots from that member * to itself and multiply that with the target spacing to arrive at its mining timestamp. * The fact that the federation can change at any time adds extra complexity to this basic approach. * The miner that mined the last block may no longer exist when the next block is about to be mined. As such * we may need to look a bit further back to find a "reference miner" that still occurs in the latest federation. */ ChainedHeader tip = this.chainIndexer.Tip; if (tip.Height < this.consensusOptions.GetMiningTimestampV2ActivationHeight) { return(GetMiningTimestampLegacy(currentTime)); } List <IFederationMember> federationMembers = this.federationHistory.GetFederationForBlock(tip, 1); if (federationMembers == null) { throw new Exception($"Could not determine the federation at block { tip.Height } + 1."); } int myIndex = federationMembers.FindIndex(m => m.PubKey == this.federationManager.CurrentFederationKey?.PubKey); if (myIndex < 0) { throw new NotAFederationMemberException(); } // Find a "reference miner" to determine our slot against. ChainedHeader referenceMinerBlock = tip; IFederationMember referenceMiner = null; int referenceMinerIndex = -1; int referenceMinerDepth = 0; for (int i = 0; i < federationMembers.Count; i++, referenceMinerDepth++) { referenceMiner = this.federationHistory.GetFederationMemberForBlock(referenceMinerBlock); referenceMinerIndex = federationMembers.FindIndex(m => m.PubKey == referenceMiner.PubKey); if (referenceMinerIndex >= 0) { break; } } if (referenceMinerIndex < 0) { throw new Exception("Could not find a member in common between the old and new federation"); } // Found a reference miner that also occurs in the latest federation. // Determine how many blocks before our mining slot. int blocksFromTipToMiningSlot = myIndex - referenceMinerIndex - referenceMinerDepth; while (blocksFromTipToMiningSlot < 0) { blocksFromTipToMiningSlot += federationMembers.Count; } // Round length in seconds. uint roundTime = (uint)this.GetRoundLength(federationMembers.Count).TotalSeconds; // Get the tip time and make is a valid time if required. uint tipTime = tip.Header.Time; if (!IsValidTimestamp(tipTime)) { tipTime += (this.consensusOptions.TargetSpacingSeconds - tipTime % this.consensusOptions.TargetSpacingSeconds); } // Check if we have missed our turn for this round. // We still consider ourselves "in a turn" if we are in the first half of the turn and we haven't mined there yet. // This might happen when starting the node for the first time or if there was a problem when mining. uint nextTimestampForMining = (uint)(tipTime + blocksFromTipToMiningSlot * this.consensusOptions.TargetSpacingSeconds); while (currentTime > nextTimestampForMining + (this.consensusOptions.TargetSpacingSeconds / 2) || // We are closer to the next turn than our own tipTime == nextTimestampForMining) { nextTimestampForMining += roundTime; } return(nextTimestampForMining); }
public override void Run(RuleContext context) { var header = context.ValidationContext.ChainedHeaderToValidate.Header as PoABlockHeader; PubKey pubKey = this.slotsManager.GetFederationMemberForTimestamp(header.Time).PubKey; if (!this.validator.VerifySignature(pubKey, header)) { // In case voting is enabled it is possible that federation was modified and another fed member signed // the header. Since voting changes are applied after max reorg blocks are passed we can tell exactly // how federation will look like max reorg blocks ahead. Code below tries to construct federation that is // expected to exist at the moment block that corresponds to header being validated was produced. Then // this federation is used to estimate who was expected to sign a block and then the signature is verified. if (this.votingEnabled) { ChainedHeader currentHeader = context.ValidationContext.ChainedHeaderToValidate; bool mightBeInsufficient = currentHeader.Height - this.chainState.ConsensusTip.Height > this.maxReorg; List <IFederationMember> modifiedFederation = this.federationManager.GetFederationMembers(); foreach (Poll poll in this.votingManager.GetFinishedPolls().Where(x => !x.IsExecuted && ((x.VotingData.Key == VoteKey.AddFederationMember) || (x.VotingData.Key == VoteKey.KickFederationMember)))) { if (currentHeader.Height - poll.PollVotedInFavorBlockData.Height <= this.maxReorg) { // Not applied yet. continue; } IFederationMember federationMember = this.consensusFactory.DeserializeFederationMember(poll.VotingData.Data); if (poll.VotingData.Key == VoteKey.AddFederationMember) { modifiedFederation.Add(federationMember); } else if (poll.VotingData.Key == VoteKey.KickFederationMember) { modifiedFederation.Remove(federationMember); } } pubKey = this.slotsManager.GetFederationMemberForTimestamp(header.Time, modifiedFederation).PubKey; if (this.validator.VerifySignature(pubKey, header)) { this.Logger.LogDebug("Signature verified using updated federation."); return; } if (mightBeInsufficient) { // Mark header as insufficient to avoid banning the peer that presented it. // When we advance consensus we will be able to validate it. context.ValidationContext.InsufficientHeaderInformation = true; } } this.Logger.LogTrace("(-)[INVALID_SIGNATURE]"); PoAConsensusErrors.InvalidHeaderSignature.Throw(); } }
public FedMemberKicked(IFederationMember kickedMember) { this.KickedMember = kickedMember; }
public void RemoveFederationMember(byte[] federationMemberBytes) { IFederationMember federationMember = this.consensusFactory.DeserializeFederationMember(federationMemberBytes); this.federationManager.RemoveFederationMember(federationMember); }
public void attack(IFederationMember h) { // attack Human Console.WriteLine(this.GetType().Name + " attacks " + h.GetType().Name); }