Exemple #1
0
        public CollateralHeightCommitmentEncoderTests()
        {
            ILogger logger = new Mock <ILogger>().Object;

            this.encoder = new CollateralHeightCommitmentEncoder(logger);
            this.r       = new Random();
        }
Exemple #2
0
        public CheckCollateralFullValidationRuleTests()
        {
            this.ibdMock = new Mock <IInitialBlockDownloadState>();
            this.collateralCheckerMock      = new Mock <ICollateralChecker>();
            this.slotsManagerMock           = new Mock <ISlotsManager>();
            this.counterChainNetworkWrapper = new CounterChainNetworkWrapper(Networks.Stratis.Mainnet());

            this.ibdMock.Setup(x => x.IsInitialBlockDownload()).Returns(false);
            this.slotsManagerMock
            .Setup(x => x.GetFederationMemberForTimestamp(It.IsAny <uint>(), null))
            .Returns(new CollateralFederationMember(new Key().PubKey, new Money(1), "addr1"));

            this.ruleContext = new RuleContext(new ValidationContext(), DateTimeOffset.Now);
            this.ruleContext.ValidationContext.BlockToValidate = new Block(new BlockHeader()
            {
                Time = 5234
            });

            Block block = this.ruleContext.ValidationContext.BlockToValidate;

            block.AddTransaction(new Transaction());

            CollateralHeightCommitmentEncoder encoder = new CollateralHeightCommitmentEncoder();

            byte[] encodedHeight = encoder.EncodeWithPrefix(1000);

            var votingOutputScript = new Script(OpcodeType.OP_RETURN, Op.GetPushOp(encodedHeight));

            block.Transactions[0].AddOutput(Money.Zero, votingOutputScript);

            this.rule        = new CheckCollateralFullValidationRule(this.ibdMock.Object, this.collateralCheckerMock.Object, this.slotsManagerMock.Object, new Mock <IDateTimeProvider>().Object, new PoANetwork(), this.counterChainNetworkWrapper);
            this.rule.Logger = new ExtendedLoggerFactory().CreateLogger(this.rule.GetType().FullName);
            this.rule.Initialize();
        }
        public CheckCollateralFullValidationRuleTests()
        {
            this.ibdMock = new Mock <IInitialBlockDownloadState>();
            this.collateralCheckerMock = new Mock <ICollateralChecker>();
            this.slotsManagerMock      = new Mock <ISlotsManager>();


            this.ibdMock.Setup(x => x.IsInitialBlockDownload()).Returns(false);
            this.slotsManagerMock
            .Setup(x => x.GetFederationMemberForTimestamp(It.IsAny <uint>(), null))
            .Returns(new CollateralFederationMember(new Key().PubKey, false, new Money(1), "addr1"));

            this.ruleContext = new RuleContext(new ValidationContext(), DateTimeOffset.Now);
            this.ruleContext.ValidationContext.BlockToValidate = new Block(new BlockHeader()
            {
                Time = 5234
            });

            Block block = this.ruleContext.ValidationContext.BlockToValidate;

            block.AddTransaction(new Transaction());

            var     loggerFactory = new ExtendedLoggerFactory();
            ILogger logger        = loggerFactory.CreateLogger(this.GetType().FullName);

            var votingDataEncoder = new VotingDataEncoder(loggerFactory);
            var votes             = new List <VotingData>
            {
                new VotingData()
                {
                    Key  = VoteKey.WhitelistHash,
                    Data = new uint256(0).ToBytes()
                }
            };

            byte[] encodedVotingData = votingDataEncoder.Encode(votes);

            var votingData = new List <byte>(VotingDataEncoder.VotingOutputPrefixBytes);

            votingData.AddRange(encodedVotingData);

            var votingOutputScript = new Script(OpcodeType.OP_RETURN, Op.GetPushOp(votingData.ToArray()));

            block.Transactions[0].AddOutput(Money.Zero, votingOutputScript);

            var commitmentHeightEncoder = new CollateralHeightCommitmentEncoder(logger);

            byte[] encodedHeight        = commitmentHeightEncoder.EncodeCommitmentHeight(1000);
            var    commitmentHeightData = new Script(OpcodeType.OP_RETURN, Op.GetPushOp(encodedHeight));

            block.Transactions[0].AddOutput(Money.Zero, commitmentHeightData);

            this.rule = new CheckCollateralFullValidationRule(this.ibdMock.Object, this.collateralCheckerMock.Object, this.slotsManagerMock.Object, new Mock <IDateTimeProvider>().Object, new PoANetwork())
            {
                Logger = logger
            };

            this.rule.Initialize();
        }
        /// <inheritdoc />
        public int?GetMultisigMinersApplicabilityHeight()
        {
            IConsensusManager consensusManager = this.fullNode.NodeService <IConsensusManager>();
            ChainedHeader     fork             = (this.lastBlockChecked == null) ? null : consensusManager.Tip.FindFork(this.lastBlockChecked);

            if (this.multisigMinersApplicabilityHeight != null && fork?.HashBlock == this.lastBlockChecked?.HashBlock)
            {
                return(this.multisigMinersApplicabilityHeight);
            }

            this.lastBlockChecked = fork;
            this.multisigMinersApplicabilityHeight = null;
            var commitmentHeightEncoder = new CollateralHeightCommitmentEncoder(this.logger);

            ChainedHeader[] headers = consensusManager.Tip.EnumerateToGenesis().TakeWhile(h => h != this.lastBlockChecked && h.Height >= this.network.CollateralCommitmentActivationHeight).Reverse().ToArray();

            ChainedHeader first = BinarySearch.BinaryFindFirst <ChainedHeader>(headers, (chainedHeader) =>
            {
                ChainedHeaderBlock block = consensusManager.GetBlockData(chainedHeader.HashBlock);
                if (block == null)
                {
                    return(null);
                }

                // Finding the height of the first STRAX collateral commitment height.
                (int?commitmentHeight, uint?magic) = commitmentHeightEncoder.DecodeCommitmentHeight(block.Block.Transactions.First());
                if (commitmentHeight == null)
                {
                    return(null);
                }

                return(magic == this.counterChainSettings.CounterChainNetwork.Magic);
            });

            this.lastBlockChecked = headers.LastOrDefault();
            this.multisigMinersApplicabilityHeight = first?.Height;

            this.UpdateMultisigMiners(first != null);

            return(this.multisigMinersApplicabilityHeight);
        }
Exemple #5
0
        // The reward each miner receives upon distribution is computed as a proportion of the overall accumulated reward since the last distribution.
        // The proportion is based on how many blocks that miner produced in the period (each miner is identified by their block's coinbase's scriptPubKey).
        // It is therefore not in any miner's advantage to delay or skip producing their blocks as it will affect their proportion of the produced blocks.
        // We pay no attention to whether a miner has been kicked since the last distribution or not.
        // If they produced an accepted block, they get their reward.

        public RewardDistributionManager(Network network, ChainIndexer chainIndexer, IConsensusManager consensusManager)
        {
            this.network          = network;
            this.chainIndexer     = chainIndexer;
            this.consensusManager = consensusManager;
            this.logger           = LogManager.GetCurrentClassLogger();

            this.encoder     = new CollateralHeightCommitmentEncoder();
            this.epoch       = this.network.Consensus.MaxReorgLength == 0 ? DefaultEpoch : (int)this.network.Consensus.MaxReorgLength;
            this.epochWindow = this.epoch * 2;

            if (this.network.RewardClaimerBlockInterval > 0)
            {
                // If the amount of blocks that the sidechain will advance in the time that the reward intervals are, is more
                // than the default epoch then use that amount so that there aren't any gaps.
                var mainchainTargetSpacingSeconds = 45;
                var sidechainAdvancement          = (int)Math.Round(this.network.RewardClaimerBlockInterval * mainchainTargetSpacingSeconds / this.network.Consensus.TargetSpacing.TotalSeconds, MidpointRounding.AwayFromZero);
                if (sidechainAdvancement > this.epoch)
                {
                    this.epoch = sidechainAdvancement;
                }
            }
        }
        /// <inheritdoc />
        public List <Recipient> Distribute(int heightOfRecordedDistributionDeposit, Money totalReward)
        {
            var encoder = new CollateralHeightCommitmentEncoder(this.logger);

            // First determine the main chain blockheight of the recorded deposit less max reorg * 2 (epoch window)
            var applicableMainChainDepositHeight = heightOfRecordedDistributionDeposit - this.epochWindow;

            this.logger.LogDebug($"{nameof(applicableMainChainDepositHeight)} : {applicableMainChainDepositHeight}");

            // Then find the header on the sidechain that contains the applicable commitment height.
            int sidechainTipHeight = this.chainIndexer.Tip.Height;

            ChainedHeader currentHeader = this.chainIndexer.GetHeader(sidechainTipHeight);

            do
            {
                if (currentHeader.Block == null)
                {
                    currentHeader.Block = this.consensusManager.GetBlockData(currentHeader.HashBlock).Block;
                }

                (int?heightOfMainChainCommitment, _) = encoder.DecodeCommitmentHeight(currentHeader.Block.Transactions[0]);
                if (heightOfMainChainCommitment != null)
                {
                    this.logger.LogDebug($"{currentHeader} : {nameof(heightOfMainChainCommitment)}={heightOfMainChainCommitment}");

                    if (heightOfMainChainCommitment <= applicableMainChainDepositHeight)
                    {
                        break;
                    }
                }

                currentHeader = currentHeader.Previous;
            } while (currentHeader.Height != 0);

            // Get the set of miners (more specifically, the scriptPubKeys they generated blocks with) to distribute rewards to.
            // Based on the computed 'common block height' we define the distribution epoch:
            int sidechainStartHeight = currentHeader.Height;

            this.logger.LogDebug($"Initial {nameof(sidechainStartHeight)} : {sidechainStartHeight}");

            // This is a special case which will not be the case on the live network.
            if (sidechainStartHeight < this.epoch)
            {
                sidechainStartHeight = 0;
            }

            // If the sidechain start is more than the epoch, then deduct the epoch window.
            if (sidechainStartHeight > this.epoch)
            {
                sidechainStartHeight -= this.epoch;
            }

            this.logger.LogDebug($"Adjusted {nameof(sidechainStartHeight)} : {sidechainStartHeight}");

            var blocksMinedEach = new Dictionary <Script, long>();

            var totalBlocks             = CalculateBlocksMinedPerMiner(blocksMinedEach, sidechainStartHeight, currentHeader.Height);
            List <Recipient> recipients = ConstructRecipients(blocksMinedEach, totalBlocks, totalReward);

            return(recipients);
        }
 public CollateralHeightCommitmentEncoderTests()
 {
     this.encoder = new CollateralHeightCommitmentEncoder();
     this.r       = new Random();
 }
Exemple #8
0
        /// <inheritdoc />
        public List <Recipient> Distribute(int heightOfRecordedDistributionDeposit, Money totalReward)
        {
            // Take a local copy of the chain tip as it might change during execution of this method.
            ChainedHeader sidechainTip = this.chainIndexer.Tip;

            // We need to determine which block on the sidechain contains a commitment to the height of the mainchain block that originated the reward transfer.
            // We otherwise do not have a common reference point from which to compute the epoch.
            // Look back at most 2x epoch lengths to avoid searching from genesis every time.

            // To avoid miners trying to disrupt the chain by committing to the same height in multiple blocks, we loop forwards and use the first occurrence
            // of a commitment with height >= the search height.

            ChainedHeader currentHeader = sidechainTip.Height > (2 * this.epoch) ? this.chainIndexer.GetHeader(sidechainTip.Height - (2 * this.epoch)) : this.chainIndexer.Genesis;

            // Cap the maximum number of iterations.
            int iterations = sidechainTip.Height - currentHeader.Height;

            var encoder = new CollateralHeightCommitmentEncoder(this.logger);

            for (int i = 0; i < iterations; i++)
            {
                int?heightOfMainChainCommitment = encoder.DecodeCommitmentHeight(currentHeader.Block.Transactions[0]);

                if (heightOfMainChainCommitment == null)
                {
                    continue;
                }

                if (heightOfMainChainCommitment >= heightOfRecordedDistributionDeposit)
                {
                    break;
                }

                // We need to ensure we walk forwards along the headers to the original tip, so if there is more than one, find the one on the common fork.
                foreach (ChainedHeader candidateHeader in currentHeader.Next)
                {
                    if (candidateHeader.FindFork(sidechainTip) != null)
                    {
                        currentHeader = candidateHeader;
                    }
                }
            }

            // Get the set of miners (more specifically, the scriptPubKeys they generated blocks with) to distribute rewards to.
            // Based on the computed 'common block height' we define the distribution epoch:
            int sidechainStartHeight = currentHeader.Height;

            // This is a special case which will not be the case on the live network.
            if (sidechainStartHeight < this.epoch)
            {
                sidechainStartHeight = 0;
            }

            // If the sidechain start is more than the epoch, then deduct the epoch window.
            if (sidechainStartHeight > this.epoch)
            {
                sidechainStartHeight -= this.epoch;
            }

            int sidechainEndHeight = currentHeader.Height;

            var blocksMinedEach = new Dictionary <Script, long>();

            long totalBlocks = 0;

            for (int currentHeight = sidechainStartHeight; currentHeight <= sidechainEndHeight; currentHeight++)
            {
                ChainedHeader chainedHeader = this.chainIndexer.GetHeader(currentHeight);
                Block         block         = chainedHeader.Block;

                Transaction coinBase = block.Transactions.First();

                // Regard the first 'spendable' scriptPubKey in the coinbase as belonging to the miner's wallet.
                // This avoids trying to recover the pubkey from the block signature.
                Script minerScript = coinBase.Outputs.First(o => !o.ScriptPubKey.IsUnspendable).ScriptPubKey;

                if (!blocksMinedEach.TryGetValue(minerScript, out long minerBlockCount))
                {
                    minerBlockCount = 0;
                }

                blocksMinedEach[minerScript] = ++minerBlockCount;

                totalBlocks++;
            }

            var recipients = new List <Recipient>();

            foreach (Script scriptPubKey in blocksMinedEach.Keys)
            {
                Money amount = totalReward * blocksMinedEach[scriptPubKey] / totalBlocks;

                recipients.Add(new Recipient()
                {
                    Amount = amount, ScriptPubKey = scriptPubKey
                });
            }

            return(recipients);
        }