Ejemplo n.º 1
0
 private static ChainedHeader ExpectChainedBlock(ChainedHeader block)
 {
     return(It.Is <ChainedHeader>(c => c.Header.GetHash() == block.Header.GetHash()));
 }
Ejemplo n.º 2
0
        /// <inheritdoc />
        public long GetTargetDepthRequired(ChainedHeader prevChainedHeader)
        {
            Guard.NotNull(prevChainedHeader, nameof(ChainedHeader));

            return((this.network.Consensus.Options as PosConsensusOptions).GetStakeMinConfirmations(prevChainedHeader.Height + 1, this.network) - 1);
        }
        /// <summary>Processes block that was added or removed from consensus chain.</summary>
        /// <returns><c>true</c> if block was processed.</returns>
        private bool ProcessBlock(Block block, ChainedHeader header)
        {
            lock (this.lockObject)
            {
                // Record outpoints.
                foreach (Transaction tx in block.Transactions)
                {
                    for (int i = 0; i < tx.Outputs.Count; i++)
                    {
                        // OP_RETURN outputs and empty outputs cannot be spent and therefore do not need to be put into the cache.
                        if (tx.Outputs[i].IsEmpty || tx.Outputs[i].ScriptPubKey.IsUnspendable)
                            continue;

                        var outPoint = new OutPoint(tx, i);

                        var outPointData = new OutPointData()
                        {
                            Outpoint = outPoint.ToString(),
                            ScriptPubKeyBytes = tx.Outputs[i].ScriptPubKey.ToBytes(),
                            Money = tx.Outputs[i].Value
                        };

                        // TODO: When the outpoint cache is full, adding outpoints singly causes overhead writing evicted entries out to the repository
                        this.outpointsRepository.AddOutPointData(outPointData);
                    }
                }
            }

            // Process inputs.
            var inputs = new List<TxIn>();

            // Collect all inputs excluding coinbases.
            foreach (TxInList inputsCollection in block.Transactions.Where(x => !x.IsCoinBase).Select(x => x.Inputs))
                inputs.AddRange(inputsCollection);

            lock (this.lockObject)
            {
                var rewindData = new AddressIndexerRewindData() { BlockHash = header.HashBlock.ToString(), BlockHeight = header.Height, SpentOutputs = new List<OutPointData>() };

                foreach (TxIn input in inputs)
                {
                    OutPoint consumedOutput = input.PrevOut;

                    if (!this.outpointsRepository.TryGetOutPointData(consumedOutput, out OutPointData consumedOutputData))
                    {
                        this.logger.LogError("Missing outpoint data for {0}.", consumedOutput);
                        this.logger.LogTrace("(-)[MISSING_OUTPOINTS_DATA]");
                        throw new Exception($"Missing outpoint data for {consumedOutput}");
                    }

                    Money amountSpent = consumedOutputData.Money;

                    rewindData.SpentOutputs.Add(consumedOutputData);
                    this.outpointsRepository.RemoveOutPointData(consumedOutput);

                    // Transactions that don't actually change the balance just bloat the database.
                    if (amountSpent == 0)
                        continue;

                    string address = this.scriptAddressReader.GetAddressFromScriptPubKey(this.network, new Script(consumedOutputData.ScriptPubKeyBytes));

                    if (string.IsNullOrEmpty(address))
                    {
                        // This condition need not be logged, as the address reader should be aware of all possible address formats already.
                        continue;
                    }

                    this.ProcessBalanceChangeLocked(header.Height, address, amountSpent, false);
                }

                // Process outputs.
                foreach (Transaction tx in block.Transactions)
                {
                    foreach (TxOut txOut in tx.Outputs)
                    {
                        Money amountReceived = txOut.Value;

                        // Transactions that don't actually change the balance just bloat the database.
                        if (amountReceived == 0 || txOut.IsEmpty || txOut.ScriptPubKey.IsUnspendable)
                            continue;

                        string address = this.scriptAddressReader.GetAddressFromScriptPubKey(this.network, txOut.ScriptPubKey);

                        if (string.IsNullOrEmpty(address))
                        {
                            // This condition need not be logged, as the address reader should be aware of all
                            // possible address formats already.
                            continue;
                        }

                        this.ProcessBalanceChangeLocked(header.Height, address, amountReceived, true);
                    }
                }

                this.outpointsRepository.RecordRewindData(rewindData);
                this.outpointsRepository.PurgeOldRewindData(this.consensusManager.Tip.Height - this.compactionTriggerDistance);
            }

            return true;
        }
        /// <summary>Constructs the headers from locator to consensus tip.</summary>
        /// <param name="getHeadersPayload">The <see cref="GetHeadersPayload"/> payload that triggered the creation of this payload.</param>
        /// <param name="lastHeader"><see cref="ChainedHeader"/> of the last header that was added to the <see cref="HeadersPayload"/>.</param>
        /// <returns>Payload with headers from locator towards consensus tip or <c>null</c> in case locator was invalid.</returns>
        protected virtual Payload ConstructHeadersPayload(GetHeadersPayload getHeadersPayload, out ChainedHeader lastHeader)
        {
            ChainedHeader fork = this.chain.FindFork(getHeadersPayload.BlockLocator);

            lastHeader = null;

            if (fork == null)
            {
                this.logger.LogTrace("(-)[INVALID_LOCATOR]:null");
                return(null);
            }

            var headersPayload = new HeadersPayload();

            ChainedHeader header = this.GetLastHeaderToSend(fork, getHeadersPayload.HashStop);

            for (int heightIndex = header.Height; heightIndex > fork.Height; heightIndex--)
            {
                lastHeader = header;

                headersPayload.Headers.Add(header.Header);

                header = header.Previous;
            }

            this.logger.LogDebug("{0} headers were selected for sending, last one is '{1}'.", headersPayload.Headers.Count, headersPayload.Headers.LastOrDefault()?.GetHash());

            // We need to reverse it as it was added to the list backwards.
            headersPayload.Headers.Reverse();

            return(headersPayload);
        }
 private ChainedHeaderBlock[] GetBlocksWithVotingRequest(int count, JoinFederationRequest votingRequest, ChainedHeader previous)
 {
     return(PoaTestHelper.GetBlocks(count, this.ChainIndexer, i => this.CreateBlockWithVotingRequest(votingRequest, i + 1), previous));
 }
Ejemplo n.º 6
0
        /// <inheritdoc />
        /// <remarks>Creates <see cref="PoAHeadersPayload"/> instead of <see cref="HeadersPayload"/> like base implementation does.</remarks>
        protected override Payload ConstructHeadersPayload(GetHeadersPayload getHeadersPayload, out ChainedHeader lastHeader)
        {
            ChainedHeader fork = this.ChainIndexer.FindFork(getHeadersPayload.BlockLocator);

            lastHeader = null;

            if (fork == null)
            {
                this.logger.LogTrace("(-)[INVALID_LOCATOR]:null");
                return(null);
            }

            var headersPayload = new PoAHeadersPayload();

            foreach (ChainedHeader chainedHeader in this.ChainIndexer.EnumerateToTip(fork).Skip(1))
            {
                lastHeader = chainedHeader;

                if (chainedHeader.Header is PoABlockHeader header)
                {
                    headersPayload.Headers.Add(header);

                    if ((chainedHeader.HashBlock == getHeadersPayload.HashStop) || (headersPayload.Headers.Count == MaxItemsPerHeadersMessage))
                    {
                        break;
                    }
                }
                else
                {
                    throw new Exception("Not a PoA header!");
                }
            }

            this.logger.LogDebug("{0} headers were selected for sending, last one is '{1}'.", headersPayload.Headers.Count, headersPayload.Headers.LastOrDefault()?.GetHash());

            return(headersPayload);
        }
Ejemplo n.º 7
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>();

            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);
        }
 /// <inheritdoc />
 public IMaturedBlockDeposits ExtractMaturedBlockDeposits(ChainedHeader chainedHeader)
 {
     return(this.depositExtractor.ExtractBlockDeposits(this.GetNewlyMaturedBlock(chainedHeader)));
 }
Ejemplo n.º 9
0
        /// <summary>Distributes download job's headers to peers that can provide blocks represented by those headers.</summary>
        /// <remarks>
        /// If some of the blocks from the job can't be provided by any peer those headers will be added to a <param name="failedHashes"></param>.
        /// <para>
        /// Have to be locked by <see cref="queueLock"/>.
        /// </para>
        /// <para>
        /// Node's quality score is being considered as a weight during the random distribution of the hashes to download among the nodes.
        /// </para>
        /// </remarks>
        /// <param name="downloadJob">Download job to be partially of fully consumed.</param>
        /// <param name="failedHashes">List of failed hashes which will be extended in case there is no peer to claim required hash.</param>
        /// <param name="emptySlots">Number of empty slots. This is the maximum number of assignments that can be created.</param>
        /// <returns>List of downloads that were distributed between the peers.</returns>
        private List <AssignedDownload> DistributeHeadersLocked(DownloadJob downloadJob, List <uint256> failedHashes, int emptySlots)
        {
            var newAssignments = new List <AssignedDownload>();

            HashSet <IBlockPullerBehavior> peerBehaviors;

            lock (this.peerLock)
            {
                peerBehaviors = new HashSet <IBlockPullerBehavior>(this.pullerBehaviorsByPeerId.Values);
            }

            bool jobFailed = false;

            if (peerBehaviors.Count == 0)
            {
                this.logger.LogDebug("There are no peers that can participate in download job distribution! Job ID {0} failed.", downloadJob.Id);
                jobFailed = true;
            }

            int lastSucceededIndex = -1;

            for (int index = 0; (index < downloadJob.Headers.Count) && (index < emptySlots) && !jobFailed; index++)
            {
                ChainedHeader header = downloadJob.Headers[index];

                while (!jobFailed)
                {
                    // Weighted random selection based on the peer's quality score.
                    double sumOfQualityScores = peerBehaviors.Sum(x => x.QualityScore);
                    double scoreToReachPeer   = this.random.NextDouble() * sumOfQualityScores;

                    IBlockPullerBehavior selectedBehavior = peerBehaviors.First();

                    foreach (IBlockPullerBehavior peerBehavior in peerBehaviors)
                    {
                        if (peerBehavior.QualityScore >= scoreToReachPeer)
                        {
                            selectedBehavior = peerBehavior;
                            break;
                        }

                        scoreToReachPeer -= peerBehavior.QualityScore;
                    }

                    INetworkPeer attachedPeer = selectedBehavior.AttachedPeer;

                    // Behavior's tip can't be null because we only have behaviors inserted in the behaviors structure after the tip is set.
                    if ((attachedPeer != null) && (selectedBehavior.Tip.FindAncestorOrSelf(header) != null))
                    {
                        int peerId = attachedPeer.Connection.Id;

                        // Assign to this peer.
                        newAssignments.Add(new AssignedDownload()
                        {
                            PeerId       = peerId,
                            JobId        = downloadJob.Id,
                            AssignedTime = this.dateTimeProvider.GetUtcNow(),
                            Header       = header
                        });

                        lastSucceededIndex = index;

                        this.logger.LogTrace("Block '{0}' was assigned to peer ID {1}.", header.HashBlock, peerId);
                        break;
                    }
                    else
                    {
                        // Peer doesn't claim this header.
                        peerBehaviors.Remove(selectedBehavior);

                        if (peerBehaviors.Count != 0)
                        {
                            continue;
                        }

                        jobFailed = true;
                        this.logger.LogDebug("Job {0} failed because there is no peer claiming header '{1}'.", downloadJob.Id, header);
                    }
                }
            }

            if (!jobFailed)
            {
                downloadJob.Headers.RemoveRange(0, lastSucceededIndex + 1);
            }
            else
            {
                int removeFrom = (lastSucceededIndex == -1) ? 0 : lastSucceededIndex + 1;

                IEnumerable <uint256> failed = downloadJob.Headers.GetRange(removeFrom, downloadJob.Headers.Count - removeFrom).Select(x => x.HashBlock);
                failedHashes.AddRange(failed);

                downloadJob.Headers.Clear();
            }

            return(newAssignments);
        }
Ejemplo n.º 10
0
        /// <inheritdoc />
        public override async Task RunAsync(RuleContext context)
        {
            this.Logger.LogTrace("()");

            Block            block = context.BlockValidationContext.Block;
            ChainedHeader    index = context.BlockValidationContext.ChainedHeader;
            DeploymentFlags  flags = context.Flags;
            UnspentOutputSet view  = context.Set;

            this.Parent.PerformanceCounter.AddProcessedBlocks(1);

            long  sigOpsCost  = 0;
            Money fees        = Money.Zero;
            var   checkInputs = new List <Task <bool> >();

            for (int txIndex = 0; txIndex < block.Transactions.Count; txIndex++)
            {
                this.Parent.PerformanceCounter.AddProcessedTransactions(1);
                Transaction tx = block.Transactions[txIndex];
                if (!context.SkipValidation)
                {
                    // TODO: Simplify this condition.
                    if (!tx.IsCoinBase && (!context.IsPoS || (context.IsPoS && !tx.IsCoinStake)))
                    {
                        if (!view.HaveInputs(tx))
                        {
                            this.Logger.LogTrace("(-)[BAD_TX_NO_INPUT]");
                            ConsensusErrors.BadTransactionMissingInput.Throw();
                        }

                        var prevheights = new int[tx.Inputs.Count];
                        // Check that transaction is BIP68 final.
                        // BIP68 lock checks (as opposed to nLockTime checks) must
                        // be in ConnectBlock because they require the UTXO set.
                        for (int j = 0; j < tx.Inputs.Count; j++)
                        {
                            prevheights[j] = (int)view.AccessCoins(tx.Inputs[j].PrevOut.Hash).Height;
                        }

                        if (!tx.CheckSequenceLocks(prevheights, index, flags.LockTimeFlags))
                        {
                            this.Logger.LogTrace("(-)[BAD_TX_NON_FINAL]");
                            ConsensusErrors.BadTransactionNonFinal.Throw();
                        }
                    }

                    // GetTransactionSignatureOperationCost counts 3 types of sigops:
                    // * legacy (always),
                    // * p2sh (when P2SH enabled in flags and excludes coinbase),
                    // * witness (when witness enabled in flags and excludes coinbase).
                    sigOpsCost += this.GetTransactionSignatureOperationCost(tx, view, flags);
                    if (sigOpsCost > this.PowConsensusOptions.MaxBlockSigopsCost)
                    {
                        this.Logger.LogTrace("(-)[BAD_BLOCK_SIG_OPS]");
                        ConsensusErrors.BadBlockSigOps.Throw();
                    }

                    // TODO: Simplify this condition.
                    if (!tx.IsCoinBase && (!context.IsPoS || (context.IsPoS && !tx.IsCoinStake)))
                    {
                        this.CheckInputs(tx, view, index.Height);
                        fees += view.GetValueIn(tx) - tx.TotalOut;
                        var txData = new PrecomputedTransactionData(tx);
                        for (int inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++)
                        {
                            this.Parent.PerformanceCounter.AddProcessedInputs(1);
                            TxIn  input          = tx.Inputs[inputIndex];
                            int   inputIndexCopy = inputIndex;
                            TxOut txout          = view.GetOutputFor(input);
                            var   checkInput     = new Task <bool>(() =>
                            {
                                var checker             = new TransactionChecker(tx, inputIndexCopy, txout.Value, txData);
                                var ctx                 = new ScriptEvaluationContext(this.Parent.Network);
                                ctx.ScriptVerify        = flags.ScriptFlags;
                                bool verifyScriptResult = ctx.VerifyScript(input.ScriptSig, txout.ScriptPubKey, checker);

                                if (verifyScriptResult == false)
                                {
                                    this.Logger.LogTrace("Verify script for transaction '{0}' failed, ScriptSig = '{1}', ScriptPubKey = '{2}', script evaluation error = '{3}'", tx.GetHash(), input.ScriptSig, txout.ScriptPubKey, ctx.Error);
                                }

                                return(verifyScriptResult);
                            });
                            checkInput.Start();
                            checkInputs.Add(checkInput);
                        }
                    }
                }

                this.UpdateCoinView(context, tx);
            }

            if (!context.SkipValidation)
            {
                this.CheckBlockReward(context, fees, index.Height, block);

                foreach (Task <bool> checkInput in checkInputs)
                {
                    if (await checkInput.ConfigureAwait(false))
                    {
                        continue;
                    }

                    this.Logger.LogTrace("(-)[BAD_TX_SCRIPT]");
                    ConsensusErrors.BadTransactionScriptError.Throw();
                }
            }
            else
            {
                this.Logger.LogTrace("BIP68, SigOp cost, and block reward validation skipped for block at height {0}.", index.Height);
            }

            this.Logger.LogTrace("(-)");
        }
 /// <summary>Sets the internal store tip and exposes the store tip to other components through the chain state.</summary>
 private void SetStoreTip(ChainedHeader newTip)
 {
     this.storeTip = newTip;
     this.chainState.BlockStoreTip = newTip;
 }
Ejemplo n.º 12
0
 /// <inheritdoc />
 public virtual void Initialize(ChainedHeader chainTip)
 {
 }
Ejemplo n.º 13
0
        protected async Task <ChainedHeader> MineBlockAtTimestampAsync(uint timestamp)
        {
            ChainedHeader tip = this.consensusManager.Tip;

            // Timestamp should always be greater than prev one.
            if (timestamp <= tip.Header.Time)
            {
                // Can happen only when target spacing had crazy low value or key was compromised and someone is mining with our key.
                this.logger.LogWarning("Somehow another block was connected with greater timestamp. Dropping current block.");
                this.logger.LogTrace("(-)[ANOTHER_BLOCK_CONNECTED]:null");
                return(null);
            }

            Script walletScriptPubKey = this.GetScriptPubKeyFromWallet();

            if (walletScriptPubKey == null)
            {
                this.logger.LogWarning("Miner wasn't able to get address from the wallet! You will not receive any rewards.");
                walletScriptPubKey = new Script();
            }

            BlockTemplate blockTemplate = this.blockDefinition.Build(tip, walletScriptPubKey);

            this.FillBlockTemplate(blockTemplate, out bool dropTemplate);

            if (dropTemplate)
            {
                this.logger.LogTrace("(-)[DROPPED]:null");
                return(null);
            }

            blockTemplate.Block.Header.Time = timestamp;

            // Update merkle root.
            blockTemplate.Block.UpdateMerkleRoot();

            // Sign block with our private key.
            var header = blockTemplate.Block.Header as PoABlockHeader;

            this.poaHeaderValidator.Sign(this.federationManager.CurrentFederationKey, header);

            ChainedHeader chainedHeader = await this.consensusManager.BlockMinedAsync(blockTemplate.Block).ConfigureAwait(false);

            if (chainedHeader == null)
            {
                // Block wasn't accepted because we already connected block from the network.
                this.logger.LogTrace("(-)[FAILED_TO_CONNECT]:null");
                return(null);
            }

            ValidationContext result = this.integrityValidator.VerifyBlockIntegrity(chainedHeader, blockTemplate.Block);

            if (result.Error != null)
            {
                // Sanity check. Should never happen.
                this.logger.LogTrace("(-)[INTEGRITY_FAILURE]");
                throw new Exception(result.Error.ToString());
            }

            return(chainedHeader);
        }
Ejemplo n.º 14
0
 public void SetWalletTip(ChainedHeader tip)
 {
     this.walletTip = tip;
 }
        /// <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.BuildAnnouncedHeaderPayload(this.chainState.BlockStoreTip.Height, 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.BuildAnnouncedHeaderPayload(this.chainState.BlockStoreTip.Height, 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 ((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;
            }
        }
Ejemplo n.º 16
0
        public async Task <ListSinceBlockModel> ListSinceBlockAsync(string blockHash, int targetConfirmations = 1)
        {
            ChainedHeader headerBlock = null;

            if (!string.IsNullOrEmpty(blockHash) && uint256.TryParse(blockHash, out uint256 hashBlock))
            {
                headerBlock = this.ChainIndexer.GetHeader(hashBlock);
            }

            if (!string.IsNullOrEmpty(blockHash) && headerBlock == null)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
            }

            if (targetConfirmations < 1)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_PARAMETER, "Invalid parameter");
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();
            Wallet wallet = this.walletManager.GetWallet(accountReference.WalletName);

            IEnumerable <TransactionData> transactions = wallet.GetAllTransactions();

            var model = new ListSinceBlockModel();

            foreach (TransactionData transactionData in transactions)
            {
                GetTransactionModel transaction = this.GetTransaction(transactionData.Id.ToString());

                int blockHeight = transactionData.BlockHeight ?? 0;

                if (headerBlock != null && blockHeight < headerBlock.Height)
                {
                    continue;
                }

                if (transaction.Confirmations < targetConfirmations)
                {
                    continue;
                }


                ListSinceBlockTransactionCategoryModel category = GetListSinceBlockTransactionCategoryModel(transaction);

                model.Transactions.Add(new ListSinceBlockTransactionModel
                {
                    Confirmations   = transaction.Confirmations,
                    BlockHash       = transaction.BlockHash,
                    BlockIndex      = transaction.BlockIndex,
                    BlockTime       = transaction.BlockTime,
                    TransactionId   = transaction.TransactionId,
                    TransactionTime = transaction.TransactionTime,
                    TimeReceived    = transaction.TimeReceived,
                    Account         = accountReference.AccountName,
                    Address         = transactionData.ScriptPubKey?.GetDestinationAddress(this.Network)?.ToString(),
                    Amount          = transaction.Amount,
                    Category        = category,
                    Fee             = transaction.Fee
                });
            }

            model.LastBlock = this.ChainIndexer.Tip.HashBlock;

            return(model);
        }
 /// <summary>
 /// Pre-fetches UTXOs for a block and some blocks with higher height.
 /// Pre-fetching is done on the background.
 /// </summary>
 /// <param name="header">Header of a block that was fully validated and requires pre-fetching.</param>
 public void Prefetch(ChainedHeader header)
 {
     this.headersQueue.Enqueue(header);
 }
        /// <summary>
        /// Constructs a block template which will be passed to consensus.
        /// </summary>
        /// <param name="chainTip">Tip of the chain that this instance will work with without touching any shared chain resources.</param>
        /// <param name="scriptPubKey">Script that explains what conditions must be met to claim ownership of a coin.</param>
        /// <returns>The contructed <see cref="Miner.BlockTemplate"/>.</returns>
        protected void OnBuild(ChainedHeader chainTip, Script scriptPubKey)
        {
            this.Configure();

            this.ChainTip = chainTip;

            this.block        = this.BlockTemplate.Block;
            this.scriptPubKey = scriptPubKey;

            this.CreateCoinbase();
            this.ComputeBlockVersion();

            // TODO: MineBlocksOnDemand
            // -regtest only: allow overriding block.nVersion with
            // -blockversion=N to test forking scenarios
            //if (this.network. chainparams.MineBlocksOnDemand())
            //    pblock->nVersion = GetArg("-blockversion", pblock->nVersion);

            this.MedianTimePast = Utils.DateTimeToUnixTime(this.ChainTip.GetMedianTimePast());
            this.LockTimeCutoff = MempoolValidator.StandardLocktimeVerifyFlags.HasFlag(Transaction.LockTimeFlags.MedianTimePast)
                ? this.MedianTimePast
                : this.block.Header.Time;

            // TODO: Implement Witness Code
            // Decide whether to include witness transactions
            // This is only needed in case the witness softfork activation is reverted
            // (which would require a very deep reorganization) or when
            // -promiscuousmempoolflags is used.
            // TODO: replace this with a call to main to assess validity of a mempool
            // transaction (which in most cases can be a no-op).
            this.IncludeWitness = false; //IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()) && fMineWitnessTx;

            // add transactions from the mempool
            int nPackagesSelected;
            int nDescendantsUpdated;

            this.AddTransactions(out nPackagesSelected, out nDescendantsUpdated);

            this.LastBlockTx     = this.BlockTx;
            this.LastBlockSize   = this.BlockSize;
            this.LastBlockWeight = this.BlockWeight;

            // TODO: Implement Witness Code
            // pblocktemplate->CoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus());
            this.BlockTemplate.VTxFees[0] = -this.fees;

            var powCoinviewRule = this.ConsensusLoop.ConsensusRules.GetRule <PowCoinViewRule>();

            this.coinbase.Outputs[0].Value = this.fees + powCoinviewRule.GetProofOfWorkReward(this.height);
            this.BlockTemplate.TotalFee    = this.fees;

            int nSerializeSize = this.block.GetSerializedSize();

            this.logger.LogDebug("Serialized size is {0} bytes, block weight is {1}, number of txs is {2}, tx fees are {3}, number of sigops is {4}.", nSerializeSize, powCoinviewRule.GetBlockWeight(this.block), this.BlockTx, this.fees, this.BlockSigOpsCost);

            this.OnUpdateHeaders();

            //pblocktemplate->TxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]);

            this.OnTestBlockValidity();

            //int64_t nTime2 = GetTimeMicros();

            //LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart));
        }
Ejemplo n.º 19
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)
        {
            if (this.consensus.BIP9Deployments[deployment] == null)
            {
                return(ThresholdState.Failed);
            }

            int            period      = this.consensus.MinerConfirmationWindow;
            long           threshold   = this.consensus.BIP9Deployments[deployment].Threshold;
            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);
        }
 /// <summary>Logic specific as to how the block will be built.</summary>
 public abstract BlockTemplate Build(ChainedHeader chainTip, Script scriptPubKey);
Ejemplo n.º 21
0
        public SortedDictionary <int, ProvenBlockHeader> ConvertToDictionaryOfProvenHeaders(ChainedHeader tip)
        {
            var headers = new SortedDictionary <int, ProvenBlockHeader>();

            ChainedHeader currentHeader = tip;

            while (currentHeader.Height != 0)
            {
                headers.Add(currentHeader.Height, currentHeader.Header as ProvenBlockHeader);

                currentHeader = currentHeader.Previous;
            }

            return(headers);
        }
Ejemplo n.º 22
0
        public IEnumerable <ChainedHeader> GetHeadersFromFork(INetworkPeer peer, ChainedHeader currentTip, uint256 hashStop = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            this.AssertStateAsync(peer, NetworkPeerState.HandShaked, cancellationToken).GetAwaiter().GetResult();

            using (var listener = new NetworkPeerListener(peer, this.GetOrCreateAsyncProvider()))
            {
                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()
                    {
                        BlockLocator = awaited,
                        HashStop     = hashStop
                    }, cancellationToken).GetAwaiter().GetResult();

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

                        using (CancellationTokenSource 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;
                                ChainedHeader 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 ChainedHeader(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.
                        }
                    }
                }
            }
        }
 private ChainedHeaderBlock[] GetBlocksWithVotingData(int count, VotingData votingData, ChainedHeader previous)
 {
     return(PoaTestHelper.GetBlocks(count, this.ChainIndexer, i => this.CreateBlockWithVotingData(new List <VotingData>()
     {
         votingData
     }, i + 1), previous));
 }
        private ChainedHeader AppendBlock(params ConcurrentChain[] chains)
        {
            ChainedHeader index = null;

            return(this.AppendBlock(index, chains));
        }
Ejemplo n.º 25
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);
        }
Ejemplo n.º 26
0
        private ChainedHeader AppendBlock(params ChainIndexer[] chainsIndexer)
        {
            ChainedHeader index = null;

            return(this.AppendBlock(index, chainsIndexer));
        }
        private async Task IndexAddressesContinuouslyAsync()
        {
            Stopwatch watch = Stopwatch.StartNew();

            while (!this.cancellation.IsCancellationRequested)
            {
                if (DateTime.Now - this.lastFlushTime > this.flushChangesInterval)
                {
                    this.logger.LogDebug("Flushing changes.");

                    lock (this.lockObject)
                    {
                        this.addressIndexRepository.SaveAllItems();
                        this.outpointsRepository.SaveAllItems();
                        this.tipDataStore.Update(this.tipData);
                    }

                    this.lastFlushTime = DateTime.Now;

                    this.logger.LogDebug("Flush completed.");
                }

                if (this.cancellation.IsCancellationRequested)
                    break;

                ChainedHeader nextHeader = this.consensusManager.Tip.GetAncestor(this.IndexerTip.Height + 1);

                if (nextHeader == null)
                {
                    this.logger.LogDebug("Next header wasn't found. Waiting.");

                    try
                    {
                        await Task.Delay(DelayTimeMs, this.cancellation.Token).ConfigureAwait(false);
                    }
                    catch (OperationCanceledException)
                    {
                    }

                    continue;
                }

                if (nextHeader.Previous.HashBlock != this.IndexerTip.HashBlock)
                {
                    ChainedHeader lastCommonHeader = nextHeader.FindFork(this.IndexerTip);

                    this.logger.LogDebug("Reorganization detected. Rewinding till '{0}'.", lastCommonHeader);

                    lock (this.lockObject)
                    {
                        // The cache doesn't really lend itself to handling a reorg very well.
                        // Therefore, we leverage LiteDb's indexing capabilities to tell us
                        // which records are for the affected blocks.
                        // TODO: May also be efficient to run ProcessBlocks with inverted deposit flags instead, depending on size of reorg

                        List<string> affectedAddresses = this.addressIndexRepository.GetAddressesHigherThanHeight(lastCommonHeader.Height);

                        foreach (string address in affectedAddresses)
                        {
                            AddressIndexerData indexData = this.addressIndexRepository.GetOrCreateAddress(address);
                            indexData.BalanceChanges.RemoveAll(x => x.BalanceChangedHeight > lastCommonHeader.Height);
                        }

                        // Rewind all the way back to the fork point.
                        while (this.IndexerTip.HashBlock != lastCommonHeader.HashBlock)
                        {
                            this.outpointsRepository.Rewind(this.IndexerTip.HashBlock);
                            this.IndexerTip = this.IndexerTip.Previous;
                        }

                        this.tipData.TipHashBytes = this.IndexerTip.HashBlock.ToBytes();
                        this.tipData.Height = this.IndexerTip.Height;
                    }

                    continue;
                }

                // First try to see if it's prefetched.
                ChainedHeaderBlock prefetchedBlock = this.prefetchingTask == null ? null : await this.prefetchingTask.ConfigureAwait(false);

                Block blockToProcess;

                if (prefetchedBlock != null && prefetchedBlock.ChainedHeader == nextHeader)
                    blockToProcess = prefetchedBlock.Block;
                else
                    blockToProcess = this.consensusManager.GetBlockData(nextHeader.HashBlock).Block;

                if (blockToProcess == null)
                {
                    this.logger.LogDebug("Next block wasn't found. Waiting.");

                    try
                    {
                        await Task.Delay(DelayTimeMs, this.cancellation.Token).ConfigureAwait(false);
                    }
                    catch (OperationCanceledException)
                    {
                    }

                    continue;
                }

                // Schedule prefetching of the next block;
                ChainedHeader headerToPrefetch = this.consensusManager.Tip.GetAncestor(nextHeader.Height + 1);

                if (headerToPrefetch != null)
                    this.prefetchingTask = Task.Run(() => this.consensusManager.GetBlockData(headerToPrefetch.HashBlock));

                watch.Restart();

                bool success = this.ProcessBlock(blockToProcess, nextHeader);

                watch.Stop();
                this.averageTimePerBlock.AddSample(watch.Elapsed.TotalMilliseconds);

                if (!success)
                {
                    this.logger.LogDebug("Failed to process next block. Waiting.");

                    try
                    {
                        await Task.Delay(DelayTimeMs, this.cancellation.Token).ConfigureAwait(false);
                    }
                    catch (OperationCanceledException)
                    {
                    }

                    continue;
                }

                this.IndexerTip = nextHeader;

                lock (this.lockObject)
                {
                    this.tipData.TipHashBytes = this.IndexerTip.HashBlock.ToBytes();
                    this.tipData.Height = this.IndexerTip.Height;
                }
            }

            lock (this.lockObject)
            {
                this.addressIndexRepository.SaveAllItems();
                this.outpointsRepository.SaveAllItems();
                this.tipDataStore.Update(this.tipData);
            }
        }
        /// <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)
        {
            if (getBlocksPayload.BlockLocators.Blocks.Count > BlockLocator.MaxLocatorSize)
            {
                this.logger.LogTrace("Peer '{0}' sent getblocks with oversized locator, disconnecting.", peer.RemoteSocketEndpoint);

                peer.Disconnect("Peer sent getblocks with oversized locator");

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

            // 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.chainState.BlockStoreTip;

            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)
            {
                // If we reached the limmit size of inv, we need to tell the downloader to send another 'getblocks' message.
                if (count == InvPayload.MaxGetBlocksInventorySize && lastAddedChainedHeader != null)
                {
                    this.logger.LogTrace("Setting peer's last block sent to '{0}'.", lastAddedChainedHeader);
                    this.lastSentHeader = lastAddedChainedHeader;
                    this.consensusManagerBehavior.UpdateBestSentHeader(this.lastSentHeader);

                    // 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.");
            }
        }
 public BlockTemplate BuildPosBlock(ChainedHeader chainTip, Script script)
 {
     return(this.blockDefinition.Build(chainTip, script));
 }
Ejemplo n.º 30
0
 /// <inheritdoc />
 public List <IFederationMember> GetFederationForBlock(ChainedHeader chainedHeader)
 {
     return((this.votingManager == null) ? this.federationManager.GetFederationMembers() : this.votingManager.GetModifiedFederation(chainedHeader));
 }