/// <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);
        }
        /// <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);
        }
Exemple #3
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);
        }