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);
            }
        }
Esempio n. 4
0
        /// <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);
        }
Esempio n. 6
0
        /// <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;
            }
        }
Esempio n. 7
0
        /// <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;
            }
        }
Esempio n. 8
0
        /// <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);
        }
Esempio n. 9
0
        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;
            }
        }
Esempio n. 10
0
        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));
        }
Esempio n. 11
0
        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()));
            }
        }
Esempio n. 12
0
        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);
        }
Esempio n. 13
0
        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.");
                    }
                }
            }
        }
Esempio n. 14
0
        /// <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));
        }
Esempio n. 16
0
        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);
            }
        }
Esempio n. 17
0
        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);
        }
Esempio n. 18
0
        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);
        }
Esempio n. 20
0
        /// <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);
        }
Esempio n. 21
0
        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);
            }
        }
Esempio n. 22
0
        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();
        }
Esempio n. 23
0
        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;
            }
        }
Esempio n. 24
0
        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();
            }
        }
Esempio n. 28
0
 public FedMemberKicked(IFederationMember kickedMember)
 {
     this.KickedMember = kickedMember;
 }
Esempio n. 29
0
        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);
 }