Exemplo n.º 1
0
        /// <summary>Called when a <see cref="Block"/> is added to the <see cref="blocksQueue"/>.
        /// Depending on the <see cref="WalletTip"/> and incoming block height, this method will decide whether the <see cref="Block"/> will be processed by the <see cref="WalletManager"/>.
        /// </summary>
        /// <param name="block">Block to be processed.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        private async Task OnProcessBlockAsync(Block block, CancellationToken cancellationToken)
        {
            Guard.NotNull(block, nameof(block));

            long currentBlockQueueSize = Interlocked.Add(ref this.blocksQueueSize, -block.BlockSize.Value);

            this.logger.LogTrace("Queue sized changed to {0} bytes.", currentBlockQueueSize);

            ChainedHeader newTip = this.chainIndexer.GetHeader(block.GetHash());

            if (newTip == null)
            {
                this.logger.LogTrace("(-)[NEW_TIP_REORG]");
                return;
            }

            // If the new block's previous hash is not the same as the one we have, there might have been a reorg.
            // If the new block follows the previous one, just pass the block to the manager.
            if (block.Header.HashPrevBlock != this.walletTip.HashBlock)
            {
                // If previous block does not match there might have
                // been a reorg, check if the wallet is still on the main chain.
                ChainedHeader inBestChain = this.chainIndexer.GetHeader(this.walletTip.HashBlock);
                if (inBestChain == null)
                {
                    // The current wallet hash was not found on the main chain.
                    // A reorg happened so bring the wallet back top the last known fork.
                    ChainedHeader fork = this.walletTip;

                    // We walk back the chained block object to find the fork.
                    while (this.chainIndexer.GetHeader(fork.HashBlock) == null)
                    {
                        fork = fork.Previous;
                    }

                    this.logger.LogInformation("Reorg detected, going back from '{0}' to '{1}'.", this.walletTip, fork);

                    this.walletManager.RemoveBlocks(fork);
                    this.walletTip = fork;

                    this.logger.LogTrace("Wallet tip set to '{0}'.", this.walletTip);
                }

                // The new tip can be ahead or behind the wallet.
                // If the new tip is ahead we try to bring the wallet up to the new tip.
                // If the new tip is behind we just check the wallet and the tip are in the same chain.
                if (newTip.Height > this.walletTip.Height)
                {
                    ChainedHeader findTip = newTip.FindAncestorOrSelf(this.walletTip);
                    if (findTip == null)
                    {
                        this.logger.LogTrace("(-)[NEW_TIP_AHEAD_NOT_IN_WALLET]");
                        return;
                    }

                    this.logger.LogTrace("Wallet tip '{0}' is behind the new tip '{1}'.", this.walletTip, newTip);

                    ChainedHeader next = this.walletTip;
                    while (next != newTip)
                    {
                        // While the wallet is catching up the entire node will wait.
                        // If a wallet is recovered to a date in the past. Consensus will stop until the wallet is up to date.

                        // TODO: This code should be replaced with a different approach
                        // Similar to BlockStore the wallet should be standalone and not depend on consensus.
                        // The block should be put in a queue and pushed to the wallet in an async way.
                        // If the wallet is behind it will just read blocks from store (or download in case of a pruned node).

                        next = newTip.GetAncestor(next.Height + 1);
                        Block nextblock = null;
                        int   index     = 0;
                        while (true)
                        {
                            if (cancellationToken.IsCancellationRequested)
                            {
                                this.logger.LogTrace("(-)[CANCELLATION_REQUESTED]");
                                return;
                            }

                            nextblock = this.blockStore.GetBlock(next.HashBlock);
                            if (nextblock == null)
                            {
                                // The idea in this abandoning of the loop is to release consensus to push the block.
                                // That will make the block available in the next push from consensus.
                                index++;
                                if (index > 10)
                                {
                                    this.logger.LogTrace("(-)[WALLET_CATCHUP_INDEX_MAX]");
                                    return;
                                }

                                // Really ugly hack to let store catch up.
                                // This will block the entire consensus pulling.
                                this.logger.LogWarning("Wallet is behind the best chain and the next block is not found in store.");
                                Thread.Sleep(100);
                                continue;
                            }

                            break;
                        }

                        this.walletTip = next;
                        this.walletManager.ProcessBlock(nextblock, next);
                    }
                }
                else
                {
                    ChainedHeader findTip = this.walletTip.FindAncestorOrSelf(newTip);
                    if (findTip == null)
                    {
                        this.logger.LogTrace("(-)[NEW_TIP_BEHIND_NOT_IN_WALLET]");
                        return;
                    }

                    this.logger.LogTrace("Wallet tip '{0}' is ahead or equal to the new tip '{1}'.", this.walletTip, newTip);
                }
            }
            else
            {
                this.logger.LogTrace("New block follows the previously known block '{0}'.", this.walletTip);
            }

            this.walletTip = newTip;
            this.walletManager.ProcessBlock(block, newTip);
        }
Exemplo n.º 2
0
        /// <inheritdoc />
        public async Task AnnounceBlocksAsync(List <ChainedHeader> blocksToAnnounce)
        {
            Guard.NotNull(blocksToAnnounce, nameof(blocksToAnnounce));
            this.logger.LogTrace("({0}.{1}:{2})", nameof(blocksToAnnounce), nameof(blocksToAnnounce.Count), blocksToAnnounce.Count);

            if (!blocksToAnnounce.Any())
            {
                this.logger.LogTrace("(-)[NO_BLOCKS]");
                return;
            }

            INetworkPeer peer = this.AttachedPeer;

            if (peer == null)
            {
                this.logger.LogTrace("(-)[NO_PEER]");
                return;
            }

            bool revertToInv = ((!this.PreferHeaders &&
                                 (!this.preferHeaderAndIDs || blocksToAnnounce.Count > 1)) ||
                                blocksToAnnounce.Count > MAX_BLOCKS_TO_ANNOUNCE);

            this.logger.LogTrace("Block propagation preferences of the peer '{0}': prefer headers - {1}, prefer headers and IDs - {2}, will{3} revert to 'inv' now.", peer.RemoteSocketEndpoint, this.PreferHeaders, this.preferHeaderAndIDs, revertToInv ? "" : " NOT");

            var headers = new List <BlockHeader>();
            var inventoryBlockToSend = new List <uint256>();

            try
            {
                ChainedHeader highestHeader = this.GetBestHeader();

                ChainedHeader bestIndex = null;
                if (!revertToInv)
                {
                    bool foundStartingHeader = false;

                    // Try to find first chained block that the peer doesn't have, and then add all chained blocks past that one.
                    foreach (ChainedHeader chainedHeader in blocksToAnnounce)
                    {
                        bestIndex = chainedHeader;

                        if (!foundStartingHeader)
                        {
                            this.logger.LogTrace("Checking is the peer '{0}' can connect header '{1}'.", peer.RemoteSocketEndpoint, chainedHeader);

                            // Peer doesn't have a block at the height of our block and with the same hash?
                            if (highestHeader?.FindAncestorOrSelf(chainedHeader) != null)
                            {
                                this.logger.LogTrace("Peer '{0}' already has header '{1}'.", peer.RemoteSocketEndpoint, chainedHeader.Previous);
                                continue;
                            }

                            // Peer doesn't have a block at the height of our block.Previous and with the same hash?
                            if (highestHeader?.FindAncestorOrSelf(chainedHeader.Previous) == null)
                            {
                                // Peer doesn't have this header or the prior one - nothing will connect, so bail out.
                                this.logger.LogTrace("Neither the header nor its previous header found for peer '{0}', reverting to 'inv'.", peer.RemoteSocketEndpoint);
                                revertToInv = true;
                                break;
                            }

                            this.logger.LogTrace("Peer '{0}' can connect header '{1}'.", peer.RemoteSocketEndpoint, chainedHeader.Previous);
                            foundStartingHeader = true;
                        }

                        // If we reached here then it means that we've found starting header.
                        headers.Add(chainedHeader.Header);
                    }
                }

                if (!revertToInv && headers.Any())
                {
                    if ((headers.Count == 1) && this.preferHeaderAndIDs)
                    {
                        // TODO:
                    }
                    else if (this.PreferHeaders)
                    {
                        if (headers.Count > 1)
                        {
                            this.logger.LogDebug("Sending {0} headers, range {1} - {2}, to peer '{3}'.", headers.Count, headers.First(), headers.Last(), peer.RemoteSocketEndpoint);
                        }
                        else
                        {
                            this.logger.LogDebug("Sending header '{0}' to peer '{1}'.", headers.First(), peer.RemoteSocketEndpoint);
                        }

                        this.lastHeaderSent = bestIndex;

                        await peer.SendMessageAsync(new HeadersPayload(headers.ToArray())).ConfigureAwait(false);

                        this.logger.LogTrace("(-)[SEND_HEADERS_PAYLOAD]");
                        return;
                    }
                    else
                    {
                        revertToInv = true;
                    }
                }

                if (revertToInv)
                {
                    // If falling back to using an inv, just try to inv the tip.
                    // The last entry in 'blocksToAnnounce' was our tip at some point in the past.
                    if (blocksToAnnounce.Any())
                    {
                        ChainedHeader chainedHeader = blocksToAnnounce.Last();
                        if (chainedHeader != null)
                        {
                            if ((highestHeader == null) || (highestHeader.GetAncestor(chainedHeader.Height) == null))
                            {
                                inventoryBlockToSend.Add(chainedHeader.HashBlock);
                                this.logger.LogDebug("Sending inventory hash '{0}' to peer '{1}'.", chainedHeader.HashBlock, peer.RemoteSocketEndpoint);
                            }
                        }
                    }
                }

                if (inventoryBlockToSend.Any())
                {
                    await this.SendAsBlockInventoryAsync(peer, inventoryBlockToSend).ConfigureAwait(false);

                    this.logger.LogTrace("(-)[SEND_INVENTORY]");
                    return;
                }
            }
            catch (OperationCanceledException)
            {
                this.logger.LogTrace("(-)[CANCELED_EXCEPTION]");
                return;
            }

            this.logger.LogTrace("(-)");
        }
        public async Task ReorgChain_FailsFullValidation_Reconnect_OldChain_Nodes_ConnectedAsync()
        {
            using (var builder = NodeBuilder.Create(this))
            {
                var bitcoinNoValidationRulesNetwork = new BitcoinRegTestNoValidationRules();

                var minerA = builder.CreateStratisPowNode(this.powNetwork).WithDummyWallet().WithReadyBlockchainData(ReadyBlockchain.BitcoinRegTest10Miner);
                var minerB = builder.CreateStratisPowNode(bitcoinNoValidationRulesNetwork).NoValidation().WithDummyWallet().Start();

                ChainedHeader minerBChainTip      = null;
                bool          interceptorsEnabled = false;
                bool          minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain = false;
                bool          minerA_IsConnecting_To_MinerBChain = false;
                bool          minerA_Disconnected_MinerBsChain   = false;
                bool          minerA_Reconnected_Its_OwnChain    = false;

                // Configure the interceptor to intercept when Miner A connects Miner B's chain.
                void interceptorConnect(ChainedHeaderBlock chainedHeaderBlock)
                {
                    if (!interceptorsEnabled)
                    {
                        return;
                    }

                    if (!minerA_IsConnecting_To_MinerBChain)
                    {
                        if (chainedHeaderBlock.ChainedHeader.Height == 12)
                        {
                            minerA_IsConnecting_To_MinerBChain = minerA.FullNode.ConsensusManager().Tip.HashBlock == minerBChainTip.GetAncestor(12).HashBlock;
                        }

                        return;
                    }

                    if (!minerA_Reconnected_Its_OwnChain)
                    {
                        if (chainedHeaderBlock.ChainedHeader.Height == 14)
                        {
                            minerA_Reconnected_Its_OwnChain = true;
                        }

                        return;
                    }
                }

                // Configure the interceptor to intercept when Miner A disconnects Miner B's chain after the reorg.
                void interceptorDisconnect(ChainedHeaderBlock chainedHeaderBlock)
                {
                    if (!interceptorsEnabled)
                    {
                        return;
                    }

                    if (!minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain)
                    {
                        if (minerA.FullNode.ConsensusManager().Tip.Height == 10)
                        {
                            minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain = true;
                        }

                        return;
                    }

                    if (!minerA_Disconnected_MinerBsChain)
                    {
                        if (minerA.FullNode.ConsensusManager().Tip.Height == 10)
                        {
                            minerA_Disconnected_MinerBsChain = true;
                        }

                        return;
                    }
                }

                minerA.SetConnectInterceptor(interceptorConnect);
                minerA.SetDisconnectInterceptor(interceptorDisconnect);
                minerA.Start();

                // Miner B syncs with Miner A
                TestHelper.ConnectAndSync(minerB, minerA);

                // Disable Miner A from sending blocks to Miner B
                TestHelper.DisableBlockPropagation(minerA, minerB);

                // Miner A continues to mine to height 14
                TestHelper.MineBlocks(minerA, 4);
                TestHelper.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 14);
                Assert.Equal(10, minerB.FullNode.ConsensusManager().Tip.Height);

                // Enable the interceptors so that they are active during the reorg.
                interceptorsEnabled = true;

                // Miner B mines 5 more blocks:
                // Block 6,7,9,10 = valid
                // Block 8 = invalid
                minerBChainTip = await TestHelper.BuildBlocks.OnNode(minerB).Amount(5).Invalid(13, (coreNode, block) => BlockBuilder.InvalidCoinbaseReward(coreNode, block)).BuildAsync();

                Assert.Equal(15, minerBChainTip.Height);
                Assert.Equal(15, minerB.FullNode.ConsensusManager().Tip.Height);

                // Wait until Miner A disconnected its own chain so that it can connect to
                // Miner B's longer chain.
                TestHelper.WaitLoop(() => minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain);

                // Wait until Miner A has connected Miner B's chain (but failed)
                TestHelper.WaitLoop(() => minerA_IsConnecting_To_MinerBChain);

                // Wait until Miner A has disconnected Miner B's invalid chain.
                TestHelper.WaitLoop(() => minerA_Disconnected_MinerBsChain);

                // Wait until Miner A has reconnected its own chain.
                TestHelper.WaitLoop(() => minerA_Reconnected_Its_OwnChain);
            }
        }
Exemplo n.º 4
0
        /// <summary>
        /// Determines the state of a BIP from the cache and/or the chain header history and the corresponding version bits.
        /// </summary>
        /// <param name="indexPrev">The previous header of the chain header to determine the states for.</param>
        /// <param name="deployment">The deployment to check the state of.</param>
        /// <returns>The current state of the deployment.</returns>
        public ThresholdState GetState(ChainedHeader indexPrev, int deployment)
        {
            int            period      = this.consensus.MinerConfirmationWindow;
            int            threshold   = this.consensus.RuleChangeActivationThreshold;
            DateTimeOffset?timeStart   = this.consensus.BIP9Deployments[deployment]?.StartTime;
            DateTimeOffset?timeTimeout = this.consensus.BIP9Deployments[deployment]?.Timeout;

            // Check if this deployment is always active.
            if (timeStart == Utils.UnixTimeToDateTime(BIP9DeploymentsParameters.AlwaysActive))
            {
                return(ThresholdState.Active);
            }

            // A block's state is always the same as that of the first of its period, so it is computed based on a pindexPrev whose height equals a multiple of nPeriod - 1.
            if (indexPrev != null)
            {
                indexPrev = indexPrev.GetAncestor(indexPrev.Height - ((indexPrev.Height + 1) % period));
            }

            // Walk backwards in steps of nPeriod to find a pindexPrev whose information is known.
            var vToCompute = new List <ChainedHeader>();

            while (!this.ContainsKey(indexPrev?.HashBlock, deployment))
            {
                if (indexPrev.GetMedianTimePast() < timeStart)
                {
                    // Optimization: don't recompute down further, as we know every earlier block will be before the start time.
                    this.Set(indexPrev?.HashBlock, deployment, ThresholdState.Defined);
                    break;
                }

                vToCompute.Add(indexPrev);
                indexPrev = indexPrev.GetAncestor(indexPrev.Height - period);
            }

            // At this point, cache[pindexPrev] is known.
            this.Assert(this.ContainsKey(indexPrev?.HashBlock, deployment));
            ThresholdState state = this.Get(indexPrev?.HashBlock, deployment);

            // Now walk forward and compute the state of descendants of pindexPrev.
            while (vToCompute.Count != 0)
            {
                ThresholdState stateNext = state;
                indexPrev = vToCompute[vToCompute.Count - 1];
                vToCompute.RemoveAt(vToCompute.Count - 1);

                switch (state)
                {
                case ThresholdState.Defined:
                {
                    if (indexPrev.GetMedianTimePast() >= timeTimeout)
                    {
                        stateNext = ThresholdState.Failed;
                    }
                    else if (indexPrev.GetMedianTimePast() >= timeStart)
                    {
                        stateNext = ThresholdState.Started;
                    }

                    break;
                }

                case ThresholdState.Started:
                {
                    if (indexPrev.GetMedianTimePast() >= timeTimeout)
                    {
                        stateNext = ThresholdState.Failed;
                        break;
                    }

                    // Counts the "votes" in the confirmation window to determine
                    // whether the rule change activation threshold has been met.
                    ChainedHeader pindexCount = indexPrev;
                    int           count       = 0;
                    for (int i = 0; i < period; i++)
                    {
                        if (this.Condition(pindexCount, deployment))
                        {
                            count++;
                        }

                        pindexCount = pindexCount.Previous;
                    }

                    // If the threshold has been met then lock in the BIP activation.
                    if (count >= threshold)
                    {
                        stateNext = ThresholdState.LockedIn;
                    }

                    break;
                }

                case ThresholdState.LockedIn:
                {
                    // Always progresses into ACTIVE.
                    stateNext = ThresholdState.Active;
                    break;
                }

                case ThresholdState.Failed:
                case ThresholdState.Active:
                {
                    // Nothing happens, these are terminal states.
                    break;
                }
                }

                this.Set(indexPrev?.HashBlock, deployment, state = stateNext);
            }

            return(state);
        }
Exemplo n.º 5
0
        /// <summary>
        /// Processes "getblocks" message received from the peer.
        /// </summary>
        /// <param name="peer">Peer that sent the message.</param>
        /// <param name="getBlocksPayload">Payload of "getblocks" message to process.</param>
        private async Task ProcessGetBlocksAsync(INetworkPeer peer, GetBlocksPayload getBlocksPayload)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(getBlocksPayload), getBlocksPayload);

            // We only want to work with blocks that are in the store,
            // so we first get information about the store's tip.
            ChainedHeader blockStoreTip = this.chain.GetBlock(this.blockRepository.BlockHash);

            if (blockStoreTip == null)
            {
                this.logger.LogTrace("(-)[REORG]");
                return;
            }

            this.logger.LogTrace("Block store tip is '{0}'.", blockStoreTip);

            // Now we want to find the last common block between our chain and the block locator the peer sent us.
            ChainedHeader chainTip  = this.chain.Tip;
            ChainedHeader forkPoint = null;

            // Find last common block between our chain and the block locator the peer sent us.
            while (forkPoint == null)
            {
                forkPoint = this.chain.FindFork(getBlocksPayload.BlockLocators.Blocks);
                if (forkPoint == null)
                {
                    this.logger.LogTrace("(-)[NO_FORK_POINT]");
                    return;
                }

                // In case of reorg, we just try again, eventually we succeed.
                if (chainTip.FindAncestorOrSelf(forkPoint) == null)
                {
                    chainTip  = this.chain.Tip;
                    forkPoint = null;
                }
            }

            this.logger.LogTrace("Fork point is '{0}'.", forkPoint);

            // If block store is lower than the fork point, or it is on different chain, we don't have anything to contribute to this peer at this point.
            if (blockStoreTip.FindAncestorOrSelf(forkPoint) == null)
            {
                this.logger.LogTrace("(-)[FORK_OUTSIDE_STORE]");
                return;
            }

            // Now we compile a list of blocks we want to send to the peer as inventory vectors.
            // This is needed as we want to traverse the chain in forward direction.
            int           maxHeight    = Math.Min(blockStoreTip.Height, forkPoint.Height + InvPayload.MaxGetBlocksInventorySize);
            ChainedHeader lastBlock    = blockStoreTip.GetAncestor(maxHeight);
            int           headersCount = maxHeight - forkPoint.Height;

            this.logger.LogTrace("Last block to announce is '{0}', number of blocks to announce is {1}.", lastBlock, headersCount);

            var headersToAnnounce = new ChainedHeader[headersCount];

            for (int i = headersCount - 1; i >= 0; i--)
            {
                headersToAnnounce[i] = lastBlock;
                lastBlock            = lastBlock.Previous;
            }

            // Now we compile inventory payload and we also consider hash stop given by the peer.
            bool          sendContinuation       = true;
            ChainedHeader lastAddedChainedHeader = null;
            var           inv = new InvPayload();

            for (int i = 0; i < headersToAnnounce.Length; i++)
            {
                ChainedHeader chainedHeader = headersToAnnounce[i];
                if (chainedHeader.HashBlock == getBlocksPayload.HashStop)
                {
                    this.logger.LogTrace("Hash stop has been reached.");
                    break;
                }

                this.logger.LogTrace("Adding block '{0}' to the inventory.", chainedHeader);
                lastAddedChainedHeader = chainedHeader;
                inv.Inventory.Add(new InventoryVector(InventoryType.MSG_BLOCK, chainedHeader.HashBlock));
                if (chainedHeader.HashBlock == chainTip.HashBlock)
                {
                    this.logger.LogTrace("Tip of the chain has been reached.");
                    sendContinuation = false;
                }
            }

            int count = inv.Inventory.Count;

            if (count > 0)
            {
                ChainedHeader highestHeader = this.GetBestHeader();

                if (highestHeader?.Height < lastAddedChainedHeader.Height)
                {
                    this.logger.LogTrace("Setting peer's last block sent to '{0}'.", lastAddedChainedHeader);
                    this.lastHeaderSent = lastAddedChainedHeader;

                    // Set last item of the batch (unless we are announcing the tip), which is then used
                    // when the peer sends us "getdata" message. When we detect "getdata" message for this block,
                    // we will send continuation inventory message. This will cause the peer to ask for another batch of blocks.
                    // See ProcessGetDataAsync method.
                    if (sendContinuation)
                    {
                        this.getBlocksBatchLastItemHash = lastAddedChainedHeader.HashBlock;
                    }
                }

                this.logger.LogTrace("Sending inventory with {0} block hashes.", count);
                await peer.SendMessageAsync(inv).ConfigureAwait(false);
            }
            else
            {
                this.logger.LogTrace("Nothing to send.");
            }

            this.logger.LogTrace("(-)");
        }
        /// <summary>
        /// Computes the metrics of all BIP9 deployments for a given block.
        /// </summary>
        /// <param name="indexPrev">The block at which to compute the metrics.</param>
        /// <param name="thresholdStates">The current state of each BIP9 deployment.</param>
        /// <returns>A <see cref="ThresholdStateModel" /> object containg the metrics.</returns>
        public List <ThresholdStateModel> GetThresholdStateMetrics(ChainedHeader indexPrev, ThresholdState[] thresholdStates)
        {
            var thresholdStateModels = new List <ThresholdStateModel>();

            ThresholdState[] array = new ThresholdState[this.consensus.BIP9Deployments.Length];

            for (int deploymentIndex = 0; deploymentIndex < array.Length; deploymentIndex++)
            {
                if (this.consensus.BIP9Deployments[deploymentIndex] == null)
                {
                    continue;
                }

                string deploymentName = this.consensus.BIP9Deployments[deploymentIndex]?.Name;

                DateTime?timeStart   = this.consensus.BIP9Deployments[deploymentIndex]?.StartTime.Date;
                DateTime?timeTimeout = this.consensus.BIP9Deployments[deploymentIndex]?.Timeout.Date;
                long     threshold   = this.consensus.BIP9Deployments[deploymentIndex].Threshold;

                int votes         = 0;
                int currentHeight = indexPrev.Height + 1;
                int period        = this.consensus.MinerConfirmationWindow;

                // First ancestor outside last confirmation window. If we haven't reached block height 2016 yet this will be the genesis block.
                int           periodStart        = (indexPrev.Height - (currentHeight % period)) > 0 ? (indexPrev.Height - (currentHeight % period)) : 0;
                ChainedHeader periodStartsHeader = indexPrev.GetAncestor(periodStart);

                int periodEndsHeight = periodStartsHeader.Height + period;

                var hexVersions = new Dictionary <string, int>();
                int totalBlocks = 0;

                ChainedHeader headerTemp = indexPrev;

                while (headerTemp != periodStartsHeader)
                {
                    if (this.Condition(headerTemp, deploymentIndex))
                    {
                        votes++;
                    }

                    totalBlocks++;

                    string hexVersion = headerTemp.Header.Version.ToString("X8");

                    if (!hexVersions.TryGetValue(hexVersion, out int count))
                    {
                        count = 0;
                    }

                    hexVersions[hexVersion] = count + 1;

                    headerTemp = headerTemp.Previous;
                }

                // look in the cache for the hash of the first block an item was deployed

                var firstSeenHash = this.cache.FirstOrDefault(c => c.Value[deploymentIndex] == ThresholdState.Started);
                int sinceHeight   = 0;

                if (firstSeenHash.Key != null)
                {
                    sinceHeight = indexPrev.FindAncestorOrSelf(firstSeenHash.Key).Height;
                }

                thresholdStateModels.Add(new ThresholdStateModel()
                {
                    DeploymentName     = deploymentName,
                    DeploymentIndex    = deploymentIndex,
                    ConfirmationPeriod = period,
                    Blocks             = totalBlocks,
                    Votes             = votes,
                    HexVersions       = hexVersions,
                    TimeStart         = timeStart,
                    TimeTimeOut       = timeTimeout,
                    Threshold         = threshold,
                    Height            = currentHeight,
                    SinceHeight       = sinceHeight,
                    PeriodStartHeight = periodStartsHeader.Height,
                    PeriodEndHeight   = periodEndsHeight,
                    StateValue        = thresholdStates[deploymentIndex],
                    ThresholdState    = ((ThresholdState)thresholdStates[deploymentIndex]).ToString()
                });
            }

            return(thresholdStateModels);
        }
Exemplo n.º 7
0
        public Target GetWorkRequired(ChainedHeader chainedHeaderToValidate, XRCConsensus consensus)
        {
            // Genesis block.
            if (chainedHeaderToValidate.Height == 0)
            {
                return(consensus.PowLimit2);
            }

            var XRCConsensusProtocol = (XRCConsensusProtocol)consensus.ConsensusFactory.Protocol;

            //hard fork
            if (chainedHeaderToValidate.Height == XRCConsensusProtocol.PowLimit2Height + 1)
            {
                return(consensus.PowLimit);
            }

            //hard fork 2 - DigiShield + X11
            if (chainedHeaderToValidate.Height > XRCConsensusProtocol.PowDigiShieldX11Height)
            {
                return(GetWorkRequiredDigiShield(chainedHeaderToValidate, consensus));
            }

            Target proofOfWorkLimit;

            // Hard fork to higher difficulty
            if (chainedHeaderToValidate.Height > XRCConsensusProtocol.PowLimit2Height)
            {
                proofOfWorkLimit = consensus.PowLimit;
            }
            else
            {
                proofOfWorkLimit = consensus.PowLimit2;
            }

            ChainedHeader lastBlock = chainedHeaderToValidate.Previous;
            int           height    = chainedHeaderToValidate.Height;

            if (lastBlock == null)
            {
                return(proofOfWorkLimit);
            }

            long difficultyAdjustmentInterval = GetDifficultyAdjustmentInterval(consensus);

            // Only change once per interval.
            if ((height) % difficultyAdjustmentInterval != 0)
            {
                if (consensus.PowAllowMinDifficultyBlocks)
                {
                    // Special difficulty rule for testnet:
                    // If the new block's timestamp is more than 2* 10 minutes
                    // then allow mining of a min-difficulty block.
                    if (chainedHeaderToValidate.Header.BlockTime > (lastBlock.Header.BlockTime + TimeSpan.FromTicks(consensus.TargetSpacing.Ticks * 2)))
                    {
                        return(proofOfWorkLimit);
                    }

                    // Return the last non-special-min-difficulty-rules-block.
                    ChainedHeader chainedHeader = lastBlock;
                    while ((chainedHeader.Previous != null) && ((chainedHeader.Height % difficultyAdjustmentInterval) != 0) && (chainedHeader.Header.Bits == proofOfWorkLimit))
                    {
                        chainedHeader = chainedHeader.Previous;
                    }

                    return(chainedHeader.Header.Bits);
                }

                return(lastBlock.Header.Bits);
            }

            // Go back by what we want to be 14 days worth of blocks.
            long pastHeight = lastBlock.Height - (difficultyAdjustmentInterval - 1);

            ChainedHeader firstChainedHeader = chainedHeaderToValidate.GetAncestor((int)pastHeight);

            if (firstChainedHeader == null)
            {
                throw new NotSupportedException("Can only calculate work of a full chain");
            }

            if (consensus.PowNoRetargeting)
            {
                return(lastBlock.Header.Bits);
            }

            // Limit adjustment step.
            TimeSpan actualTimespan = lastBlock.Header.BlockTime - firstChainedHeader.Header.BlockTime;

            if (actualTimespan < TimeSpan.FromTicks(consensus.TargetTimespan.Ticks / 4))
            {
                actualTimespan = TimeSpan.FromTicks(consensus.TargetTimespan.Ticks / 4);
            }
            if (actualTimespan > TimeSpan.FromTicks(consensus.TargetTimespan.Ticks * 4))
            {
                actualTimespan = TimeSpan.FromTicks(consensus.TargetTimespan.Ticks * 4);
            }

            // Retarget.
            BigInteger newTarget = lastBlock.Header.Bits.ToBigInteger();

            newTarget = newTarget.Multiply(BigInteger.ValueOf((long)actualTimespan.TotalSeconds));
            newTarget = newTarget.Divide(BigInteger.ValueOf((long)consensus.TargetTimespan.TotalSeconds));

            var finalTarget = new Target(newTarget);

            if (finalTarget > proofOfWorkLimit)
            {
                finalTarget = proofOfWorkLimit;
            }

            return(finalTarget);
        }
        /// <summary>
        /// Check if transaction will be BIP 68 final in the next block to be created.
        /// Simulates calling SequenceLocks() with data from the tip of the current active chain.
        /// Optionally stores in LockPoints the resulting height and time calculated and the hash
        /// of the block needed for calculation or skips the calculation and uses the LockPoints
        /// passed in for evaluation.
        /// The LockPoints should not be considered valid if CheckSequenceLocks returns false.
        /// See consensus/consensus.h for flag definitions.
        /// </summary>
        /// <param name="network">The blockchain network.</param>
        /// <param name="tip">Tip of the chain.</param>
        /// <param name="context">Validation context for the memory pool.</param>
        /// <param name="flags">Transaction lock time flags.</param>
        /// <param name="lp">Optional- existing lock points to use, and update during evaluation.</param>
        /// <param name="useExistingLockPoints">Whether to use the existing lock points during evaluation.</param>
        /// <returns>Whether sequence lock validated.</returns>
        /// <seealso cref="SequenceLock.Evaluate(ChainedHeader)"/>
        public static bool CheckSequenceLocks(Network network, ChainedHeader tip, MempoolValidationContext context, Transaction.LockTimeFlags flags, LockPoints lp = null, bool useExistingLockPoints = false)
        {
            Block dummyBlock = network.Consensus.ConsensusFactory.CreateBlock();

            dummyBlock.Header.HashPrevBlock = tip.HashBlock;
            var index = new ChainedHeader(dummyBlock.Header, dummyBlock.GetHash(), tip);

            // CheckSequenceLocks() uses chainActive.Height()+1 to evaluate
            // height based locks because when SequenceLocks() is called within
            // ConnectBlock(), the height of the block *being*
            // evaluated is what is used.
            // Thus if we want to know if a transaction can be part of the
            // *next* block, we need to use one more than chainActive.Height()

            SequenceLock lockPair;

            if (useExistingLockPoints)
            {
                Guard.Assert(lp != null);
                lockPair = new SequenceLock(lp.Height, lp.Time);
            }
            else
            {
                // pcoinsTip contains the UTXO set for chainActive.Tip()
                var prevheights = new List <int>();
                foreach (TxIn txin in context.Transaction.Inputs)
                {
                    UnspentOutputs coins = context.View.GetCoins(txin.PrevOut.Hash);
                    if (coins == null)
                    {
                        return(false);
                    }

                    if (coins.Height == TxMempool.MempoolHeight)
                    {
                        // Assume all mempool transaction confirm in the next block
                        prevheights.Add(tip.Height + 1);
                    }
                    else
                    {
                        prevheights.Add((int)coins.Height);
                    }
                }
                lockPair = context.Transaction.CalculateSequenceLocks(prevheights.ToArray(), index, flags);

                if (lp != null)
                {
                    lp.Height = lockPair.MinHeight;
                    lp.Time   = lockPair.MinTime.ToUnixTimeMilliseconds();
                    // Also store the hash of the block with the highest height of
                    // all the blocks which have sequence locked prevouts.
                    // This hash needs to still be on the chain
                    // for these LockPoint calculations to be valid
                    // Note: It is impossible to correctly calculate a maxInputBlock
                    // if any of the sequence locked inputs depend on unconfirmed txs,
                    // except in the special case where the relative lock time/height
                    // is 0, which is equivalent to no sequence lock. Since we assume
                    // input height of tip+1 for mempool txs and test the resulting
                    // lockPair from CalculateSequenceLocks against tip+1.  We know
                    // EvaluateSequenceLocks will fail if there was a non-zero sequence
                    // lock on a mempool input, so we can use the return value of
                    // CheckSequenceLocks to indicate the LockPoints validity
                    int maxInputHeight = 0;
                    foreach (int height in prevheights)
                    {
                        // Can ignore mempool inputs since we'll fail if they had non-zero locks
                        if (height != tip.Height + 1)
                        {
                            maxInputHeight = Math.Max(maxInputHeight, height);
                        }
                    }

                    lp.MaxInputBlock = tip.GetAncestor(maxInputHeight);
                }
            }

            return(lockPair.Evaluate(index));
        }
        /// <summary>
        /// Computes the metrics of all BIP9 deployments for a given block.
        /// </summary>
        /// <param name="indexPrev">The block at which to compute the metrics.</param>
        /// <param name="thresholdStates">The current state of each BIP9 deployment.</param>
        /// <returns>A <see cref="ThresholdStateModel" /> object containg the metrics.</returns>
        public List <ThresholdStateModel> GetThresholdStateMetrics(ChainedHeader indexPrev, ThresholdState[] thresholdStates)
        {
            var thresholdStateModels = new List <ThresholdStateModel>();

            ThresholdState[] array = new ThresholdState[this.consensus.BIP9Deployments.Length];

            for (int deploymentIndex = 0; deploymentIndex < array.Length; deploymentIndex++)
            {
                if (this.consensus.BIP9Deployments[deploymentIndex] == null)
                {
                    continue;
                }

                DateTime?timeStart   = this.consensus.BIP9Deployments[deploymentIndex]?.StartTime.Date;
                DateTime?timeTimeout = this.consensus.BIP9Deployments[deploymentIndex]?.Timeout.Date;
                int      threshold   = this.consensus.RuleChangeActivationThreshold;

                int votes         = 0;
                int currentHeight = indexPrev.Height + 1;
                int period        = this.consensus.MinerConfirmationWindow;

                // First ancestor outside last confirmation window.
                ChainedHeader periodStartsHeader = indexPrev.GetAncestor(indexPrev.Height - (currentHeight % period));
                int           periodEndsHeight   = periodStartsHeader.Height + period;

                var hexVersions = new Dictionary <string, int>();
                int totalBlocks = 0;

                ChainedHeader headerTemp = indexPrev;

                while (headerTemp != periodStartsHeader)
                {
                    if (this.Condition(headerTemp, deploymentIndex))
                    {
                        votes++;
                    }

                    totalBlocks++;

                    string hexVersion = headerTemp.Header.Version.ToString("X8");

                    if (!hexVersions.TryGetValue(hexVersion, out int count))
                    {
                        count = 0;
                    }

                    hexVersions[hexVersion] = count + 1;

                    headerTemp = headerTemp.Previous;
                }

                thresholdStateModels.Add(new ThresholdStateModel()
                {
                    DeploymentIndex    = deploymentIndex,
                    ConfirmationPeriod = period,
                    Blocks             = totalBlocks,
                    Votes             = votes,
                    HexVersions       = hexVersions,
                    TimeStart         = timeStart,
                    TimeTimeOut       = timeTimeout,
                    Threshold         = threshold,
                    Height            = currentHeight,
                    PeriodStartHeight = periodStartsHeader.Height,
                    PeriodEndHeight   = periodEndsHeight,
                    StateValue        = thresholdStates[deploymentIndex],
                    ThresholdState    = ((ThresholdState)thresholdStates[deploymentIndex]).ToString()
                });
            }

            return(thresholdStateModels);
        }
Exemplo n.º 10
0
        /// <inheritdoc />
        public async Task <Result <List <MaturedBlockDepositsModel> > > GetMaturedDepositsAsync(int blockHeight, int maxBlocks, int maxDeposits = int.MaxValue)
        {
            ChainedHeader consensusTip = this.consensusManager.Tip;

            if (consensusTip == null)
            {
                return(Result <List <MaturedBlockDepositsModel> > .Fail("Not ready to provide blocks."));
            }

            int matureTipHeight = (consensusTip.Height - (int)this.depositExtractor.MinimumDepositConfirmations);

            if (blockHeight > matureTipHeight)
            {
                // We need to return a Result type here to explicitly indicate failure and the reason for failure.
                // This is an expected condition so we can avoid throwing an exception here.
                return(Result <List <MaturedBlockDepositsModel> > .Fail($"Block height {blockHeight} submitted is not mature enough. Blocks less than a height of {matureTipHeight} can be processed."));
            }

            var maturedBlocks = new List <MaturedBlockDepositsModel>();

            // Half of the timeout. We will also need time to convert it to json.
            int maxTimeCollectionCanTakeMs = RestApiClientBase.TimeoutMs / 2;
            var cancellation = new CancellationTokenSource(maxTimeCollectionCanTakeMs);

            int maxBlockHeight = Math.Min(matureTipHeight, blockHeight + maxBlocks - 1);

            var           headers = new List <ChainedHeader>();
            ChainedHeader header  = consensusTip.GetAncestor(maxBlockHeight);

            for (int i = maxBlockHeight; i >= blockHeight; i--)
            {
                headers.Add(header);
                header = header.Previous;
            }

            headers.Reverse();

            int numDeposits = 0;

            for (int ndx = 0; ndx < headers.Count; ndx += 100)
            {
                List <ChainedHeader> currentHeaders = headers.GetRange(ndx, Math.Min(100, headers.Count - ndx));

                List <uint256> hashes = currentHeaders.Select(h => h.HashBlock).ToList();

                ChainedHeaderBlock[] blocks = this.consensusManager.GetBlockData(hashes);

                foreach (ChainedHeaderBlock chainedHeaderBlock in blocks)
                {
                    if (chainedHeaderBlock?.Block?.Transactions == null)
                    {
                        this.logger.LogDebug("Unexpected null data. Send what we've collected.");

                        return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks));
                    }

                    MaturedBlockDepositsModel maturedBlockDeposits = this.depositExtractor.ExtractBlockDeposits(chainedHeaderBlock);

                    maturedBlocks.Add(maturedBlockDeposits);

                    numDeposits += maturedBlockDeposits.Deposits?.Count ?? 0;

                    if (maturedBlocks.Count >= maxBlocks || numDeposits >= maxDeposits)
                    {
                        return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks));
                    }

                    if (cancellation.IsCancellationRequested)
                    {
                        this.logger.LogDebug("Stop matured blocks collection because it's taking too long. Send what we've collected.");

                        return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks));
                    }
                }
            }

            return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks));
        }
        /// <inheritdoc />
        public async Task <List <MaturedBlockDepositsModel> > GetMaturedDepositsAsync(int blockHeight, int maxBlocks)
        {
            ChainedHeader consensusTip = this.consensusManager.Tip;

            int matureTipHeight = (consensusTip.Height - (int)this.depositExtractor.MinimumDepositConfirmations);

            if (blockHeight > matureTipHeight)
            {
                throw new InvalidOperationException($"Block height {blockHeight} submitted is not mature enough. Blocks less than a height of {matureTipHeight} can be processed.");
            }

            // Cache clean-up.
            lock (this.depositCache)
            {
                // The requested height gives away the fact that the peer is probably no longer interested in cached entries below that height.
                // Keep an additional 1,000 blocks anyway in case there are some parallel request that are still executing for lower heights.
                foreach (int i in this.depositCache.Where(d => d.Key < (blockHeight - 1000)).Select(d => d.Key).ToArray())
                {
                    this.depositCache.Remove(i);
                }
            }

            var maturedBlocks = new List <MaturedBlockDepositsModel>();

            // Don't spend to much time that the requester may give up.
            DateTime deadLine = DateTime.Now.AddSeconds(30);

            for (int i = blockHeight; (i <= matureTipHeight) && (i < blockHeight + maxBlocks); i++)
            {
                MaturedBlockDepositsModel maturedBlockDeposits = null;

                // First try the cache.
                lock (this.depositCache)
                {
                    this.depositCache.TryGetValue(i, out maturedBlockDeposits);
                }

                // If not in cache..
                if (maturedBlockDeposits == null)
                {
                    ChainedHeader      currentHeader = consensusTip.GetAncestor(i);
                    ChainedHeaderBlock block         = await this.consensusManager.GetBlockDataAsync(currentHeader.HashBlock).ConfigureAwait(false);

                    maturedBlockDeposits = this.depositExtractor.ExtractBlockDeposits(block);

                    if (maturedBlockDeposits == null)
                    {
                        throw new InvalidOperationException($"Unable to get deposits for block at height {currentHeader.Height}");
                    }

                    // Save this so that we don't need to scan the block again.
                    lock (this.depositCache)
                    {
                        this.depositCache[i] = maturedBlockDeposits;
                    }
                }

                maturedBlocks.Add(maturedBlockDeposits);

                if (DateTime.Now >= deadLine)
                {
                    break;
                }
            }

            return(maturedBlocks);
        }
Exemplo n.º 12
0
        /// <inheritdoc />
        public override async Task InitializeAsync()
        {
            // TODO rewrite chain starting logic. Tips manager should be used.

            await this.StartChainAsync().ConfigureAwait(false);

            ChainedHeader initializedAt = this.chainIndexer.Tip;

            if (this.provenBlockHeaderStore != null)
            {
                // If we find at this point that proven header store is behind chain we can rewind chain (this will cause a ripple effect and rewind block store and consensus)
                // This problem should go away once we implement a component to keep all tips up to date
                // https://github.com/stratisproject/StratisBitcoinFullNode/issues/2503
                initializedAt = await this.provenBlockHeaderStore.InitializeAsync(this.chainIndexer.Tip);
            }

            var rewindHeight = this.nodeSettings.ConfigReader.GetOrDefault(RewindFlag, -1);

            if (rewindHeight >= 0)
            {
                if (rewindHeight < initializedAt.Height)
                {
                    // Ensure that we don't try to rewind further than the coin view is capable of doing.
                    var utxoSet      = ((dynamic)this.consensusRules).UtxoSet;
                    var coinDatabase = ((dynamic)utxoSet).ICoindb;
                    ((dynamic)coinDatabase).Initialize(initializedAt);
                    int minRewindHeight = ((dynamic)coinDatabase).GetMinRewindHeight();
                    ((dynamic)coinDatabase).Dispose();

                    if (minRewindHeight == -1 || rewindHeight < minRewindHeight)
                    {
                        this.logger.LogWarning($"Can't rewind below block at height {minRewindHeight}. Rewind ignored.");
                    }
                    else
                    {
                        initializedAt = initializedAt.GetAncestor(rewindHeight);
                        this.logger.LogInformation($"Rewinding to block at height {rewindHeight}.");
                    }
                }
                else
                {
                    this.logger.LogWarning($"Can't rewind to block at height {rewindHeight}. Rewind ignored.");
                }

                SetRewindFlag(this.nodeSettings, -1);
            }

            if (this.chainIndexer.Tip.Height != initializedAt.Height)
            {
                this.chainIndexer.Initialize(initializedAt);
            }

            NetworkPeerConnectionParameters connectionParameters = this.connectionManager.Parameters;

            connectionParameters.IsRelay = this.connectionManager.ConnectionSettings.RelayTxes;

            connectionParameters.TemplateBehaviors.Add(new PingPongBehavior());
            connectionParameters.TemplateBehaviors.Add(new EnforcePeerVersionCheckBehavior(this.chainIndexer, this.nodeSettings, this.network, this.loggerFactory));
            connectionParameters.TemplateBehaviors.Add(new ConsensusManagerBehavior(this.chainIndexer, this.initialBlockDownloadState, this.consensusManager, this.peerBanning, this.loggerFactory));

            // TODO: Once a proper rate limiting strategy has been implemented, this check will be removed.
            if (!this.network.IsRegTest())
            {
                connectionParameters.TemplateBehaviors.Add(new RateLimitingBehavior(this.dateTimeProvider, this.loggerFactory, this.peerBanning));
            }

            connectionParameters.TemplateBehaviors.Add(new PeerBanningBehavior(this.loggerFactory, this.peerBanning, this.nodeSettings));
            connectionParameters.TemplateBehaviors.Add(new BlockPullerBehavior(this.blockPuller, this.initialBlockDownloadState, this.dateTimeProvider, this.loggerFactory));
            connectionParameters.TemplateBehaviors.Add(new ConnectionManagerBehavior(this.connectionManager, this.loggerFactory));

            this.StartAddressManager(connectionParameters);

            if (this.connectionManager.ConnectionSettings.SyncTimeEnabled)
            {
                connectionParameters.TemplateBehaviors.Add(new TimeSyncBehavior(this.timeSyncBehaviorState, this.dateTimeProvider, this.loggerFactory));
            }
            else
            {
                this.logger.LogDebug("Time synchronization with peers is disabled.");
            }

            // Block store must be initialized before consensus manager.
            // This may be a temporary solution until a better way is found to solve this dependency.
            this.blockStore.Initialize();

            // The finalized repository needs to be initialized before the rules engine in case the
            // node shutdown unexpectedly and the finalized block info needs to be reset.
            this.finalizedBlockInfoRepository.Initialize(this.chainIndexer.Tip);

            this.consensusRules.Initialize(this.chainIndexer.Tip);

            await this.consensusManager.InitializeAsync(this.chainIndexer.Tip).ConfigureAwait(false);

            this.chainState.ConsensusTip = this.consensusManager.Tip;

            this.nodeStats.RegisterStats(sb => sb.Append(this.asyncProvider.GetStatistics(!this.nodeSettings.LogSettings.DebugArgs.Any())), StatsType.Component, this.GetType().Name, 100);

            // TODO: Should this always be called?
            ((IBlockStoreQueue)this.blockStore).ReindexChain(this.consensusManager, this.nodeLifetime.ApplicationStopping);
        }