/// <inheritdoc />
        public override void Initialize(ChainedHeader chainTip)
        {
            base.Initialize(chainTip);

            var breezeCoinView = (DBreezeCoinView)((CachedCoinView)this.UtxoSet).Inner;

            breezeCoinView.Initialize();

            uint256 consensusTipHash = breezeCoinView.GetTipHash();

            while (true)
            {
                ChainedHeader pendingTip = chainTip.FindAncestorOrSelf(consensusTipHash);

                if (pendingTip != null)
                {
                    break;
                }

                this.logger.LogInformation("Rewinding coin db from {0}", consensusTipHash);
                // In case block store initialized behind, rewind until or before the block store tip.
                // The node will complete loading before connecting to peers so the chain will never know if a reorg happened.
                consensusTipHash = breezeCoinView.Rewind();
            }
        }
Пример #2
0
        /// <summary>
        /// Set the tip of <see cref="ConsensusManager"/>, if the given <paramref name="chainTip"/> is not equal to <see cref="Tip"/>
        /// then rewind consensus until a common header is found.
        /// </summary>
        /// <remarks>
        /// If <see cref="blockStore"/> is not <c>null</c> (block store is available) then all block headers in
        /// <see cref="chainedHeaderTree"/> will be marked as their block data is available.
        /// If store is not available the <see cref="ConsensusManager"/> won't be able to serve blocks from disk,
        /// instead all block requests that are not in memory will be sent to the <see cref="blockPuller"/>.
        /// </remarks>
        /// <param name="chainTip">Last common header between chain repository and block store if it's available,
        /// if the store is not available it is the chain repository tip.</param>
        public async Task InitializeAsync(ChainedHeader chainTip)
        {
            this.logger.LogTrace("({0}:'{1}')", nameof(chainTip), chainTip);

            // TODO: consensus store
            // We should consider creating a consensus store class that will internally contain
            //  coinview and it will abstract the methods `RewindAsync()` `GetBlockHashAsync()`

            uint256 consensusTipHash = await this.consensusRules.GetBlockHashAsync().ConfigureAwait(false);

            while (true)
            {
                this.Tip = chainTip.FindAncestorOrSelf(consensusTipHash);

                if (this.Tip?.HashBlock == consensusTipHash)
                {
                    break;
                }

                // In case block store initialized behind, rewind until or before the block store tip.
                // The node will complete loading before connecting to peers so the chain will never know if a reorg happened.
                consensusTipHash = await this.consensusRules.RewindAsync().ConfigureAwait(false);
            }

            this.chainedHeaderTree.Initialize(this.Tip, this.blockStore != null);

            this.logger.LogTrace("(-)");
        }
Пример #3
0
        /// <summary>Find last header that should be included in headers payload.</summary>
        protected ChainedHeader GetLastHeaderToSend(ChainedHeader fork, uint256 hashStop)
        {
            ChainedHeader lastHeader = this.ConsensusManager.Tip;

            // If the hash stop has been given, calculate the last chained header from it.
            if (hashStop != null && hashStop != uint256.Zero)
            {
                ChainedHeader hashStopHeader = lastHeader.FindAncestorOrSelf(hashStop);

                if ((hashStopHeader != null) && (lastHeader.Height > fork.Height))
                {
                    lastHeader = hashStopHeader;
                }
            }

            // Do not return more than 2000 headers from the fork point.
            if ((lastHeader.Height - fork.Height) > MaxItemsPerHeadersMessage)
            {
                // e.g. If fork = 3000 and tip is 6000 we need to start from block 5000.
                int startFromHeight = fork.Height + MaxItemsPerHeadersMessage;
                lastHeader = lastHeader.GetAncestor(startFromHeight);
            }

            return(lastHeader);
        }
Пример #4
0
        /// <summary>Updates the best sent header but only if the new value is better or is on a different chain.</summary>
        /// <param name="header">The new value to set if it is better or on a different chain.</param>
        public void UpdateBestSentHeader(ChainedHeader header)
        {
            this.logger.LogTrace("({0}:'{1}')", nameof(header), header);

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

            ChainedHeader bestSentHeader = null;

            lock (this.bestSentHeaderLock)
            {
                if (this.BestSentHeader != null)
                {
                    ChainedHeader ancestorOrSelf = header.FindAncestorOrSelf(this.BestSentHeader);

                    if (ancestorOrSelf != header)
                    {
                        this.BestSentHeader = header;
                    }
                }
                else
                {
                    this.BestSentHeader = header;
                }

                bestSentHeader = this.BestSentHeader;
            }

            this.logger.LogTrace("(-):{0}='{1}'", nameof(this.BestSentHeader), bestSentHeader);
        }
Пример #5
0
        /// <inheritdoc />
        public override void Initialize(ChainedHeader chainTip)
        {
            base.Initialize(chainTip);

            var coinDatabase = ((CachedCoinView)this.UtxoSet).ICoindb;

            coinDatabase.Initialize();

            HashHeightPair coinViewTip = coinDatabase.GetTipHash();

            while (true)
            {
                ChainedHeader pendingTip = chainTip.FindAncestorOrSelf(coinViewTip.Hash);

                if (pendingTip != null)
                {
                    break;
                }

                this.logger.LogInformation("Rewinding coin view from '{0}'.", coinViewTip);

                // If the block store was initialized behind the coin view's tip, rewind it to on or before it's tip.
                // The node will complete loading before connecting to peers so the chain will never know that a reorg happened.
                coinViewTip = coinDatabase.Rewind();
            }

            this.logger.LogInformation("Coin view rewound to '{0}'.", coinDatabase.GetTipHash());
        }
        /// <inheritdoc />
        public async Task <ChainedHeader> InitializeAsync(ChainedHeader highestHeader)
        {
            Guard.NotNull(highestHeader, nameof(highestHeader));
            await this.provenBlockHeaderRepository.InitializeAsync().ConfigureAwait(false);

            var tip     = highestHeader;
            var repoTip = this.provenBlockHeaderRepository.TipHashHeight;

            if (repoTip.Hash != tip.HashBlock)
            {
                // Repository is behind chain of headers.
                tip = tip.FindAncestorOrSelf(repoTip.Hash, repoTip.Height);

                if (tip == null)
                {
                    // Start at one less of the current repo height as we have already checked
                    // the repo tip.
                    for (var height = repoTip.Height - 1; height > 0; height--)
                    {
                        var provenBlockHeader =
                            await this.provenBlockHeaderRepository.GetAsync(height).ConfigureAwait(false);

                        // Block header at current height not found, go to previous height.
                        if (provenBlockHeader == null)
                        {
                            continue;
                        }

                        tip = highestHeader.FindAncestorOrSelf(provenBlockHeader.GetHash());
                        if (tip != null)
                        {
                            this.TipHashHeight = new HashHeightPair(provenBlockHeader.GetHash(), height);
                            break;
                        }
                    }

                    if (tip == null)
                    {
                        this.logger.LogDebug("[TIP_NOT_FOUND]:{0}", highestHeader);
                        throw new ProvenBlockHeaderException($"{highestHeader} was not found in the store.");
                    }
                }
                else
                {
                    this.TipHashHeight = new HashHeightPair(tip.HashBlock, tip.Height);
                }
            }
            else
            {
                this.TipHashHeight = new HashHeightPair(tip.HashBlock, tip.Height);
            }

            this.logger.LogDebug("Proven block header store initialized at '{0}'.", this.TipHashHeight);

            return(tip);
        }
        /// <inheritdoc />
        public override void Initialize(ChainedHeader chainTip)
        {
            base.Initialize(chainTip);

            this.StakeChain.Load();

            // A temporary hack until tip manage will be introduced.
            var           coindb    = ((CachedCoinView)this.UtxoSet).ICoindb;
            ChainedHeader coinDbTip = chainTip.FindAncestorOrSelf(coindb.GetTipHash().Hash);

            this.RewindDataIndexCache.Initialize(coinDbTip.Height, this.UtxoSet);
        }
        /// <inheritdoc />
        public override void Initialize(ChainedHeader chainTip)
        {
            base.Initialize(chainTip);

            this.StakeChain.Load();

            // A temporary hack until tip manage will be introduced.
            var           breezeCoinView = (DBreezeCoinView)((CachedCoinView)this.UtxoSet).Inner;
            uint256       hash           = breezeCoinView.GetTipHash();
            ChainedHeader tip            = chainTip.FindAncestorOrSelf(hash);

            this.RewindDataIndexCache.Initialize(tip.Height, this.UtxoSet);
        }
Пример #9
0
        /// <inheritdoc />
        public override async Task InitializeAsync(ChainedHeader chainTip)
        {
            await base.InitializeAsync(chainTip).ConfigureAwait(false);

            await this.StakeChain.LoadAsync().ConfigureAwait(false);

            // A temporary hack until tip manage will be introduced.
            var     breezeCoinView = (DBreezeCoinView)((CachedCoinView)this.UtxoSet).Inner;
            uint256 hash           = await breezeCoinView.GetTipHashAsync().ConfigureAwait(false);

            ChainedHeader tip = chainTip.FindAncestorOrSelf(hash);

            await this.RewindDataIndexCache.InitializeAsync(tip.Height, this.UtxoSet).ConfigureAwait(false);
        }
        /// <summary>
        /// Compacts the block and transaction database by recreating the tables without the deleted references.
        /// </summary>
        /// <param name="blockRepositoryTip">The last fully validated block of the node.</param>
        private void PrepareDatabaseForCompacting(ChainedHeader blockRepositoryTip)
        {
            int upperHeight = this.blockRepository.TipHashAndHeight.Height - this.storeSettings.AmountOfBlocksToKeep;

            var toDelete = new List <ChainedHeader>();

            ChainedHeader startFromHeader = blockRepositoryTip.GetAncestor(upperHeight);
            ChainedHeader endAtHeader     = blockRepositoryTip.FindAncestorOrSelf(this.PrunedTip.Hash);

            this.logger.LogInformation($"Pruning blocks from height {upperHeight} to {endAtHeader.Height}.");

            while (startFromHeader.Previous != null && startFromHeader != endAtHeader)
            {
                toDelete.Add(startFromHeader);
                startFromHeader = startFromHeader.Previous;
            }

            this.blockRepository.DeleteBlocks(toDelete.Select(cb => cb.HashBlock).ToList());

            this.UpdatePrunedTip(blockRepositoryTip.GetAncestor(upperHeight));
        }
        /// <inheritdoc />
        public override async Task InitializeAsync(ChainedHeader chainTip)
        {
            var breezeCoinView = (DBreezeCoinView)((CachedCoinView)this.UtxoSet).Inner;

            await breezeCoinView.InitializeAsync().ConfigureAwait(false);

            uint256 consensusTipHash = await breezeCoinView.GetTipHashAsync().ConfigureAwait(false);

            while (true)
            {
                ChainedHeader pendingTip = chainTip.FindAncestorOrSelf(consensusTipHash);

                if (pendingTip != null)
                {
                    break;
                }

                // In case block store initialized behind, rewind until or before the block store tip.
                // The node will complete loading before connecting to peers so the chain will never know if a reorg happened.
                consensusTipHash = await breezeCoinView.RewindAsync().ConfigureAwait(false);
            }
        }
Пример #12
0
        /// <inheritdoc />
        public void Initialize(ChainedHeader highestHeader)
        {
            if (this.commonTipPersistingTask != null)
            {
                throw new Exception("Already initialized.");
            }

            var commonTipHashHeight = this.keyValueRepo.LoadValue <HashHeightPair>(CommonTipKey);

            if (commonTipHashHeight != null)
            {
                this.lastCommonTip = highestHeader.FindAncestorOrSelf(commonTipHashHeight.Hash, commonTipHashHeight.Height);
            }
            else
            {
                // Genesis.
                this.lastCommonTip = highestHeader.GetAncestor(0);
            }

            this.logger.LogDebug("Tips manager initialized at '{0}'.", this.lastCommonTip);

            this.commonTipPersistingTask = this.PersistCommonTipContinuouslyAsync();
        }
        /// <inheritdoc />
        public async Task InitializeAsync(IConsensus consensusParameters, ChainedHeader tip, ICoinView coinView)
        {
            // A temporary hack until tip manage will be introduced.
            var     breezeCoinView = (DBreezeCoinView)((CachedCoinView)coinView).Inner;
            uint256 hash           = await breezeCoinView.GetTipHashAsync().ConfigureAwait(false);

            tip = tip.FindAncestorOrSelf(hash);

            this.numberOfBlocksToKeep = (int)consensusParameters.MaxReorgLength;

            int heightToSyncTo = tip.Height > this.numberOfBlocksToKeep ? tip.Height - this.numberOfBlocksToKeep : 0;

            for (int rewindHeight = tip.Height - 1; rewindHeight >= heightToSyncTo; rewindHeight--)
            {
                RewindData rewindData = await coinView.GetRewindData(rewindHeight).ConfigureAwait(false);

                if (rewindData == null)
                {
                    throw new ConsensusException($"Rewind data of height '{rewindHeight}' was not found!");
                }

                if (rewindData.OutputsToRestore == null || rewindData.OutputsToRestore.Count == 0)
                {
                    continue;
                }

                foreach (UnspentOutputs unspent in rewindData.OutputsToRestore)
                {
                    for (int outputIndex = 0; outputIndex < unspent.Outputs.Length; outputIndex++)
                    {
                        string key = $"{unspent.TransactionId}-{outputIndex}";
                        this.items[key] = rewindHeight;
                    }
                }
            }
        }
        /// <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)
            {
                var           chainBehavior = peer.Behavior <ChainHeadersBehavior>();
                ChainedHeader peerTip       = chainBehavior.PendingTip;

                int peersHeight = peerTip != null ? peerTip.Height : 0;
                if (peersHeight < lastAddedChainedHeader.Height)
                {
                    this.logger.LogTrace("Setting peer's pending tip to '{0}'.", lastAddedChainedHeader);
                    chainBehavior.SetPendingTip(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("(-)");
        }
Пример #15
0
        /// <inheritdoc />
        public async Task AnnounceBlocksAsync(List <ChainedHeader> blocksToAnnounce)
        {
            Guard.NotNull(blocksToAnnounce, nameof(blocksToAnnounce));

            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 > MaxBlocksToAnnounce);

            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 <ChainedHeader>();

            try
            {
                ChainedHeader bestSentHeader = this.consensusManagerBehavior.BestSentHeader;

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

                    // In case we don't have any information about peer's tip send him only last header and don't update best sent header.
                    // We expect peer to answer with getheaders message.
                    if (bestSentHeader == null)
                    {
                        await peer.SendMessageAsync(this.BuildHeadersAnnouncePayload(new[] { blocksToAnnounce.Last().Header })).ConfigureAwait(false);

                        this.logger.LogTrace("(-)[SENT_SINGLE_HEADER]");
                        return;
                    }

                    // 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 (bestSentHeader?.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 (bestSentHeader?.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.lastSentHeader = bestIndex;
                        this.consensusManagerBehavior.UpdateBestSentHeader(this.lastSentHeader);

                        await peer.SendMessageAsync(this.BuildHeadersAnnouncePayload(headers)).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 ((bestSentHeader == null) || (bestSentHeader.GetAncestor(chainedHeader.Height) == null))
                            {
                                inventoryBlockToSend.Add(chainedHeader);
                                this.logger.LogDebug("Sending inventory hash '{0}' to peer '{1}'.", chainedHeader.HashBlock, peer.RemoteSocketEndpoint);
                            }
                        }
                    }
                }

                if (inventoryBlockToSend.Any())
                {
                    this.lastSentHeader = inventoryBlockToSend.Last();
                    this.consensusManagerBehavior.UpdateBestSentHeader(this.lastSentHeader);

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

                    this.logger.LogTrace("(-)[SEND_INVENTORY]");
                    return;
                }
            }
            catch (OperationCanceledException)
            {
                this.logger.LogTrace("(-)[CANCELED_EXCEPTION]");
                return;
            }
        }
Пример #16
0
        /// <inheritdoc />
        public void ProcessBlock(Block block)
        {
            Guard.NotNull(block, nameof(block));
            this.logger.LogTrace("({0}:'{1}')", nameof(block), block.GetHash());

            ChainedHeader newTip = this.chain.GetBlock(block.GetHash());

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

            // If the new block's previous hash is the same as the
            // wallet hash then 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.chain.GetBlock(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.chain.GetBlock(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);

                    // The wallet is falling behind we need to catch up.
                    this.logger.LogWarning("New tip '{0}' is too far in advance, put the puller back.", newTip);
                    this.blockNotification.SyncFrom(this.walletTip.HashBlock);
                    return;
                }
                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.HashBlock);
                }
            }
            else
            {
                this.logger.LogTrace("New block follows the previously known block '{0}'.", this.walletTip);
            }

            this.walletTip = newTip;
            this.walletManager.ProcessBlock(block, newTip);

            this.logger.LogTrace("(-)");
        }
Пример #17
0
        /// <inheritdoc />
        public void ProcessBlock(Block block)
        {
            Guard.NotNull(block, nameof(block));

            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 the same as the
            // wallet hash then 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.LogDebug("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.LogDebug("Wallet tip '{0}' is behind the new tip '{1}'.", this.walletTip, newTip);

                    // The wallet is falling behind we need to catch up.
                    this.logger.LogWarning("New tip '{0}' is too far in advance.", newTip);

                    CancellationToken token = this.nodeLifetime.ApplicationStopping;

                    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).

                        token.ThrowIfCancellationRequested();

                        next = newTip.GetAncestor(next.Height + 1);
                        ChainedHeaderBlock nextblock = null;
                        int index = 0;
                        while (true)
                        {
                            token.ThrowIfCancellationRequested();

                            nextblock = this.consensusManager.GetBlockData(next.HashBlock);
                            if (nextblock != null && nextblock.Block != null)
                            {
                                break;
                            }

                            // 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;
                        }

                        this.walletTip = next;
                        this.walletManager.ProcessBlock(nextblock.Block, next);
                    }

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

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

            this.walletTip = newTip;
            this.walletManager.ProcessBlock(block, newTip);
        }
Пример #18
0
        /// <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>();
            var array = new ThresholdState[this.consensus.BIP9Deployments.Length];

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

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

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

                var votes         = 0;
                var currentHeight = indexPrev.Height + 1;
                var 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.
                var periodStart = indexPrev.Height - currentHeight % period > 0
                    ? indexPrev.Height - currentHeight % period
                    : 0;
                var periodStartsHeader = indexPrev.GetAncestor(periodStart);

                var periodEndsHeight = periodStartsHeader.Height + period;

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

                var headerTemp = indexPrev;

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

                    totalBlocks++;

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

                    if (!hexVersions.TryGetValue(hexVersion, out var 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);
                var 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    = thresholdStates[deploymentIndex].ToString()
                });
            }

            return(thresholdStateModels);
        }
Пример #19
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.chain.GetBlock(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.chain.GetBlock(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.chain.GetBlock(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 = await this.blockStore.GetBlockAsync(next.HashBlock).ConfigureAwait(false);

                            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);
        }
        /// <summary>
        /// Processes "headers" message received from the peer.
        /// </summary>
        /// <param name="peer">Peer from which the message was received.</param>
        /// <param name="headersPayload">Payload of "headers" message to process.</param>
        /// <remarks>
        /// "headers" message is sent in response to "getheaders" message or it is solicited
        /// by the peer when a new block is validated (unless in IBD).
        /// <para>
        /// When we receive "headers" message from the peer, we can adjust our knowledge
        /// of the peer's view of the chain. We update its pending tip, which represents
        /// the tip of the best chain we think the peer has.
        /// </para>
        /// <para>
        /// If we receive a valid header from peer which work is higher than the work
        /// of our best chain's tip, we update our view of the best chain to that tip.
        /// </para>
        /// </remarks>
        private async Task ProcessHeadersAsync(INetworkPeer peer, HeadersPayload headersPayload)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(headersPayload), headersPayload);

            if (!this.CanSync)
            {
                this.logger.LogTrace("(-)[CANT_SYNC]");
                return;
            }

            if (headersPayload.Headers.Count == 0)
            {
                this.logger.LogTrace("Headers payload with no headers was received. Assuming we're synced with the peer.");
                this.logger.LogTrace("(-)[NO_HEADERS]");
                return;
            }

            ChainedHeader pendingTipBefore = this.pendingTip;

            this.logger.LogTrace("Pending tip is '{0}', received {1} new headers.", pendingTipBefore, headersPayload.Headers.Count);

            bool doTrySync = false;

            // TODO: implement MAX_HEADERS_RESULTS in NBitcoin.HeadersPayload

            ChainedHeader tip = pendingTipBefore;

            foreach (BlockHeader header in headersPayload.Headers)
            {
                ChainedHeader prev = tip?.FindAncestorOrSelf(header.HashPrevBlock);
                if (prev == null)
                {
                    this.logger.LogTrace("Previous header of the new header '{0}' was not found on the peer's chain, the view of the peer's chain is probably outdated.", header);

                    // We have received a header from the peer for which we don't register a previous header.
                    // This can happen if our information about where the peer is is invalid.
                    // However, if the previous header is on the chain that we recognize,
                    // we can fix it.

                    // Try to find the header's previous hash on our best chain.
                    prev = this.Chain.GetBlock(header.HashPrevBlock);

                    if (prev == null)
                    {
                        this.logger.LogTrace("Previous header of the new header '{0}' was not found on our chain either.", header);

                        // If we can't connect the header we received from the peer, we might be on completely different chain or
                        // a reorg happened recently. If we ignored it, we would have invalid view of the peer and the propagation
                        // of blocks would not work well. So we ask the peer for headers using "getheaders" message.
                        // Enforce a sync.
                        doTrySync = true;
                        break;
                    }

                    // Now we know the previous block header and thus we can connect the new header.
                }

                tip = new ChainedHeader(header, header.GetHash(), prev);
                bool validated = this.Chain.GetBlock(tip.HashBlock) != null || tip.Validate(peer.Network);
                validated &= !this.chainState.IsMarkedInvalid(tip.HashBlock);
                if (!validated)
                {
                    this.logger.LogTrace("Validation of new header '{0}' failed.", tip);
                    this.InvalidHeaderReceived = true;
                    break;
                }

                this.pendingTip = tip;
            }

            if (pendingTipBefore != this.pendingTip)
            {
                this.logger.LogTrace("Pending tip changed to '{0}'.", this.pendingTip);
            }

            if ((this.pendingTip != null) && !this.bestChainSelector.TrySetAvailableTip(this.AttachedPeer.Connection.Id, this.pendingTip))
            {
                this.InvalidHeaderReceived = true;
            }

            ChainedHeader chainedPendingTip = this.pendingTip == null ? null : this.Chain.GetBlock(this.pendingTip.HashBlock);

            if (chainedPendingTip != null)
            {
                // This allows garbage collection to collect the duplicated pendingTip and ancestors.
                this.pendingTip = chainedPendingTip;
            }

            // If we made any advancement or the sync is enforced by 'doTrySync'- continue syncing.
            if (doTrySync || (this.pendingTip == null) || (pendingTipBefore == null) || (pendingTipBefore.HashBlock != this.pendingTip.HashBlock))
            {
                await this.TrySyncAsync().ConfigureAwait(false);
            }

            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;
                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;
                }

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