Beispiel #1
0
        public Task Load(ConcurrentChain chain)
        {
            Guard.Assert(chain.Tip == chain.Genesis);

            return(this._Session.Do(() =>
            {
                ChainedBlock tip = null;
                bool first = true;
                foreach (var row in this._Session.Transaction.SelectForward <int, BlockHeader>("Chain"))
                {
                    if (tip != null && row.Value.HashPrevBlock != tip.HashBlock)
                    {
                        break;
                    }
                    tip = new ChainedBlock(row.Value, null, tip);

                    if (first)
                    {
                        first = false;
                        Guard.Assert(tip.HashBlock == chain.Genesis.HashBlock); // can't swap networks
                    }
                }
                if (tip == null)
                {
                    return;
                }
                this._Locator = tip.GetLocator();
                chain.SetTip(tip);
            }));
        }
        public Task SaveAsync(ConcurrentChain chain)
        {
            Guard.NotNull(chain, nameof(chain));

            Task task = Task.Run(() =>
            {
                using (DBreeze.Transactions.Transaction transaction = this.dbreeze.GetTransaction())
                {
                    ChainedBlock fork   = this.locator == null ? null : chain.FindFork(this.locator);
                    ChainedBlock tip    = chain.Tip;
                    ChainedBlock toSave = tip;

                    List <ChainedBlock> blocks = new List <ChainedBlock>();
                    while (toSave != fork)
                    {
                        blocks.Add(toSave);
                        toSave = toSave.Previous;
                    }

                    // DBreeze is faster on ordered insert.
                    IOrderedEnumerable <ChainedBlock> orderedChainedBlocks = blocks.OrderBy(b => b.Height);
                    foreach (ChainedBlock block in orderedChainedBlocks)
                    {
                        transaction.Insert("Chain", block.Height, block.Header);
                    }

                    this.locator = tip.GetLocator();
                    transaction.Commit();
                }
            });

            return(task);
        }
Beispiel #3
0
        public void ChainedBlockVerifySkipListForGetLocator()
        {
            int mainLength   = 100000;
            int branchLength = 50000;

            // Make a main chain 100000 blocks long.
            ConcurrentChain chain = this.CreateChain(mainLength - 1);

            // Make a branch that splits off at block 49999, 50000 blocks long.
            ChainedBlock mainTip = chain.Tip;
            ChainedBlock block   = mainTip.GetAncestor(branchLength - 1);

            for (int i = 0; i < branchLength; i++)
            {
                Block newBlock = TestUtils.CreateFakeBlock();
                newBlock.Header.HashPrevBlock = block.Header.GetHash();
                block = new ChainedBlock(newBlock.Header, newBlock.Header.GetHash(), block);
            }
            ChainedBlock branchTip = block;

            // Test 100 random starting points for locators.
            Random rand = new Random();

            for (int n = 0; n < 100; n++)
            {
                // Find a random location along chain for locator < mainLength is on main chain > mainLength is on branch.
                int r = rand.Next(mainLength + branchLength);

                // Block to get locator for.
                ChainedBlock tip = r < mainLength?mainTip.GetAncestor(r) : branchTip.GetAncestor(r - mainLength);

                // Get a block locator.
                BlockLocator locator = tip.GetLocator();

                // The first result must be the block itself, the last one must be genesis.
                Assert.Equal(tip.HashBlock, locator.Blocks.First());
                Assert.Equal(chain.Genesis.HashBlock, locator.Blocks.Last());

                // Entries 1 through 11 (inclusive) go back one step each.
                for (int i = 1; (i < 12) && (i < (locator.Blocks.Count - 1)); i++)
                {
                    ChainedBlock expectedBlock = tip.GetAncestor(tip.Height - i);
                    Assert.Equal(expectedBlock.HashBlock, locator.Blocks[i]);
                }

                // The further ones (excluding the last one) go back with exponential steps.
                int dist   = 2;
                int height = tip.Height - 11 - dist;
                for (int i = 12; i < locator.Blocks.Count() - 1; i++)
                {
                    ChainedBlock expectedBlock = tip.GetAncestor(height);
                    Assert.Equal(expectedBlock.HashBlock, locator.Blocks[i]);
                    dist   *= 2;
                    height -= dist;
                }
            }
        }
Beispiel #4
0
        /// <inheritdoc />
        public void UpdateLastBlockSyncedHeight(Wallet wallet, ChainedBlock chainedBlock)
        {
            // the block locator will help when the wallet
            // needs to rewind this will be used to find the fork
            wallet.BlockLocator = chainedBlock.GetLocator().Blocks;

            // update the wallets with the last processed block height
            foreach (var accountRoot in wallet.AccountsRoot.Where(a => a.CoinType == this.coinType))
            {
                accountRoot.LastBlockSyncedHeight = chainedBlock.Height;
                accountRoot.LastBlockSyncedHash   = chainedBlock.HashBlock;
            }
        }
Beispiel #5
0
        public IEnumerable <Block> GetBlocksFromFork(ChainedBlock currentTip, uint256 hashStop = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var listener = CreateListener())
            {
                SendMessage(new GetBlocksPayload()
                {
                    BlockLocators = currentTip.GetLocator(),
                });

                var headers = GetHeadersFromFork(currentTip, hashStop, cancellationToken);

                foreach (var block in GetBlocks(headers.Select(b => b.HashBlock), cancellationToken))
                {
                    yield return(block);
                }
            }
            //GetBlocks(neededBlocks.ToEnumerable(false).Select(e => e.HashBlock), cancellationToken);
        }
Beispiel #6
0
 public IEnumerable <ChainedBlock> GetHeadersFromFork(ChainedBlock currentTip,
                                                      uint256 hashStop = null,
                                                      CancellationToken cancellationToken = default(CancellationToken))
 {
     AssertState(NodeState.HandShaked, cancellationToken);
     using (TraceCorrelation.Open())
     {
         NodeServerTrace.Information("Building chain");
         using (var listener = this.CreateListener().OfType <HeadersPayload>())
         {
             while (true)
             {
                 SendMessage(new GetHeadersPayload()
                 {
                     BlockLocators = currentTip.GetLocator(),
                     HashStop      = hashStop
                 });
                 var headers = listener.ReceivePayload <HeadersPayload>(cancellationToken);
                 if (headers.Headers.Count == 0)
                 {
                     break;
                 }
                 foreach (var header in headers.Headers)
                 {
                     var prev = currentTip.FindAncestorOrSelf(header.HashPrevBlock);
                     if (prev == null)
                     {
                         NodeServerTrace.Error("Block Header received out of order " + header.GetHash(), null);
                         throw new InvalidOperationException("Block Header received out of order");
                     }
                     currentTip = new ChainedBlock(header, header.GetHash(), prev);
                     yield return(currentTip);
                 }
                 if (currentTip.HashBlock == hashStop)
                 {
                     break;
                 }
             }
         }
     }
 }
Beispiel #7
0
        public Task LoadAsync(ConcurrentChain chain)
        {
            Guard.Assert(chain.Tip == chain.Genesis);

            Task task = Task.Run(() =>
            {
                using (DBreeze.Transactions.Transaction transaction = this.dbreeze.GetTransaction())
                {
                    ChainedBlock tip = null;
                    bool first       = true;

                    foreach (Row <int, BlockHeader> row in transaction.SelectForward <int, BlockHeader>("Chain"))
                    {
                        if ((tip != null) && (row.Value.HashPrevBlock != tip.HashBlock))
                        {
                            break;
                        }

                        tip = new ChainedBlock(row.Value, null, tip);
                        if (first)
                        {
                            first = false;
                            Guard.Assert(tip.HashBlock == chain.Genesis.HashBlock); // can't swap networks
                        }
                    }

                    if (tip == null)
                    {
                        return;
                    }

                    this.locator = tip.GetLocator();
                    chain.SetTip(tip);
                }
            });

            return(task);
        }
        private void AttachedPeer_MessageReceived(NetworkPeer peer, IncomingMessage message)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(message), message.Message.Command);

            switch (message.Message.Payload)
            {
            case InvPayload inv:
            {
                if (inv.Inventory.Any(i => ((i.Type & InventoryType.MSG_BLOCK) != 0) && !this.Chain.Contains(i.Hash)))
                {
                    // No need of periodical refresh, the peer is notifying us.
                    this.refreshTimer.Dispose();
                    if (this.AutoSync)
                    {
                        this.TrySync();
                    }
                }
                break;
            }

            case GetHeadersPayload getHeaders:
            {
                // Represents our height from the peer's point of view.
                // It is sent from the peer on first connect, in response to Inv(Block)
                // or in response to HeaderPayload until an empty array is returned.
                // This payload notifies peers of our current best validated height.
                // Use the ChainState.ConsensusTip property (not Chain.Tip)
                // if the peer is behind/equal to our best height an empty array is sent back.

                if (!this.CanRespondToGetHeaders)
                {
                    break;
                }

                // Ignoring "getheaders" from peers because node is in initial block download.
                // If not in IBD whitelisted won't be checked.
                if (this.initialBlockDownloadState.IsInitialBlockDownload() && !peer.Behavior <ConnectionManagerBehavior>().Whitelisted)
                {
                    break;
                }

                HeadersPayload headers      = new HeadersPayload();
                ChainedBlock   consensusTip = this.chainState.ConsensusTip;
                consensusTip = this.Chain.GetBlock(consensusTip.HashBlock);

                ChainedBlock fork = this.Chain.FindFork(getHeaders.BlockLocators);
                if (fork != null)
                {
                    if ((consensusTip == null) || (fork.Height > consensusTip.Height))
                    {
                        // Fork not yet validated.
                        fork = null;
                    }

                    if (fork != null)
                    {
                        foreach (ChainedBlock header in this.Chain.EnumerateToTip(fork).Skip(1))
                        {
                            if (header.Height > consensusTip.Height)
                            {
                                break;
                            }

                            headers.Headers.Add(header.Header);
                            if ((header.HashBlock == getHeaders.HashStop) || (headers.Headers.Count == 2000))
                            {
                                break;
                            }
                        }
                    }
                }

                peer.SendMessageVoidAsync(headers);
                break;
            }

            case HeadersPayload newHeaders:
            {
                // Represents the peers height from our point view.
                // This updates the pending tip parameter which is
                // the peers current best validated height.
                // If the peer's height is higher Chain.Tip is updated to have
                // the most PoW header.
                // It is sent in response to GetHeadersPayload or is solicited by the
                // peer when a new block is validated (and not in IBD).

                if (!this.CanSync)
                {
                    break;
                }

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

                // TODO: implement MAX_HEADERS_RESULTS in NBitcoin.HeadersPayload

                ChainedBlock tip = pendingTipBefore;
                foreach (BlockHeader header in newHeaders.Headers)
                {
                    ChainedBlock 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.
                            var getHeadersPayload = new GetHeadersPayload()
                            {
                                BlockLocators = pendingTipBefore.GetLocator(),
                                HashStop      = null
                            };

                            peer.SendMessageVoidAsync(getHeadersPayload);
                            break;
                        }

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

                    tip = new ChainedBlock(header, header.GetHash(peer.Network.NetworkOptions), 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.ChainWork > this.Chain.Tip.ChainWork)
                {
                    // Long reorganization protection on POS networks.
                    bool reorgPrevented = false;
                    uint maxReorgLength = this.chainState.MaxReorgLength;
                    if (maxReorgLength != 0)
                    {
                        Network      network      = peer.Network;
                        ChainedBlock consensusTip = this.chainState.ConsensusTip;
                        if ((network != null) && (consensusTip != null))
                        {
                            ChainedBlock fork = this.pendingTip.FindFork(consensusTip);
                            if ((fork != null) && (fork != consensusTip))
                            {
                                int reorgLength = consensusTip.Height - fork.Height;
                                if (reorgLength > maxReorgLength)
                                {
                                    this.logger.LogTrace("Reorganization of length {0} prevented, maximal reorganization length is {1}, consensus tip is '{2}'.", reorgLength, maxReorgLength, consensusTip);
                                    this.InvalidHeaderReceived = true;
                                    reorgPrevented             = true;
                                }
                                else
                                {
                                    this.logger.LogTrace("Reorganization of length {0} accepted, consensus tip is '{1}'.", reorgLength, consensusTip);
                                }
                            }
                        }
                    }

                    // Switch to better chain.
                    if (!reorgPrevented)
                    {
                        this.logger.LogTrace("New chain tip '{0}' selected, chain work is '{1}'.", this.pendingTip, this.pendingTip.ChainWork);
                        this.Chain.SetTip(this.pendingTip);
                    }
                }

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

                if ((!this.InvalidHeaderReceived) && (newHeaders.Headers.Count != 0) && (pendingTipBefore.HashBlock != this.GetPendingTipOrChainTip().HashBlock))
                {
                    this.TrySync();
                }

                break;
            }
            }

            this.logger.LogTrace("(-)");
        }
        public IEnumerable <ChainedBlock> GetHeadersFromFork(NetworkPeer peer, ChainedBlock currentTip, uint256 hashStop = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            this.AssertStateAsync(peer, NetworkPeerState.HandShaked, cancellationToken).GetAwaiter().GetResult();

            using (var listener = new NetworkPeerListener(peer))
            {
                int acceptMaxReorgDepth = 0;
                while (true)
                {
                    // Get before last so, at the end, we should only receive 1 header equals to this one (so we will not have race problems with concurrent GetChains).
                    BlockLocator awaited = currentTip.Previous == null?currentTip.GetLocator() : currentTip.Previous.GetLocator();

                    peer.SendMessageAsync(new GetHeadersPayload()
                    {
                        BlockLocators = awaited,
                        HashStop      = hashStop
                    }).GetAwaiter().GetResult();

                    while (true)
                    {
                        bool           isOurs  = false;
                        HeadersPayload headers = null;

                        using (var headersCancel = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
                        {
                            headersCancel.CancelAfter(TimeSpan.FromMinutes(1.0));
                            try
                            {
                                headers = listener.ReceivePayloadAsync <HeadersPayload>(headersCancel.Token).GetAwaiter().GetResult();
                            }
                            catch (OperationCanceledException)
                            {
                                acceptMaxReorgDepth += 6;
                                if (cancellationToken.IsCancellationRequested)
                                {
                                    throw;
                                }

                                // Send a new GetHeaders.
                                break;
                            }
                        }

                        // In the special case where the remote node is at height 0 as well as us, then the headers count will be 0.
                        if ((headers.Headers.Count == 0) && (peer.PeerVersion.StartHeight == 0) && (currentTip.HashBlock == peer.Network.GenesisHash))
                        {
                            yield break;
                        }

                        if ((headers.Headers.Count == 1) && (headers.Headers[0].GetHash() == currentTip.HashBlock))
                        {
                            yield break;
                        }

                        foreach (BlockHeader header in headers.Headers)
                        {
                            uint256 hash = header.GetHash();
                            if (hash == currentTip.HashBlock)
                            {
                                continue;
                            }

                            // The previous headers request timeout, this can arrive in case of big reorg.
                            if (header.HashPrevBlock != currentTip.HashBlock)
                            {
                                int          reorgDepth     = 0;
                                ChainedBlock tempCurrentTip = currentTip;
                                while (reorgDepth != acceptMaxReorgDepth && tempCurrentTip != null && header.HashPrevBlock != tempCurrentTip.HashBlock)
                                {
                                    reorgDepth++;
                                    tempCurrentTip = tempCurrentTip.Previous;
                                }

                                if (reorgDepth != acceptMaxReorgDepth && tempCurrentTip != null)
                                {
                                    currentTip = tempCurrentTip;
                                }
                            }

                            if (header.HashPrevBlock == currentTip.HashBlock)
                            {
                                isOurs     = true;
                                currentTip = new ChainedBlock(header, hash, currentTip);

                                yield return(currentTip);

                                if (currentTip.HashBlock == hashStop)
                                {
                                    yield break;
                                }
                            }
                            else
                            {
                                break;  // Not our headers, continue receive.
                            }
                        }

                        if (isOurs)
                        {
                            break;  //Go ask for next header.
                        }
                    }
                }
            }
        }
Beispiel #10
0
 public bool SaveProgress(ChainedBlock tip)
 {
     return(SaveProgress(tip.GetLocator()));
 }
 public void SaveProgress(ChainedBlock tip)
 {
     SaveProgress(tip.GetLocator());
 }