/// <summary>Called when a <see cref="Block"/> is added to the <see cref="blocksQueue"/>. /// Depending on the <see cref="WalletTip"/> and incoming block height, this method will decide whether the <see cref="Block"/> will be processed by the <see cref="WalletManager"/>. /// </summary> /// <param name="block">Block to be processed.</param> /// <param name="cancellationToken">The cancellation token.</param> private async Task OnProcessBlockAsync(Block block, CancellationToken cancellationToken) { Guard.NotNull(block, nameof(block)); long currentBlockQueueSize = Interlocked.Add(ref this.blocksQueueSize, -block.BlockSize.Value); this.logger.LogTrace("Queue sized changed to {0} bytes.", currentBlockQueueSize); ChainedHeader newTip = this.chainIndexer.GetHeader(block.GetHash()); if (newTip == null) { this.logger.LogTrace("(-)[NEW_TIP_REORG]"); return; } // If the new block's previous hash is not the same as the one we have, there might have been a reorg. // If the new block follows the previous one, just pass the block to the manager. if (block.Header.HashPrevBlock != this.walletTip.HashBlock) { // If previous block does not match there might have // been a reorg, check if the wallet is still on the main chain. ChainedHeader inBestChain = this.chainIndexer.GetHeader(this.walletTip.HashBlock); if (inBestChain == null) { // The current wallet hash was not found on the main chain. // A reorg happened so bring the wallet back top the last known fork. ChainedHeader fork = this.walletTip; // We walk back the chained block object to find the fork. while (this.chainIndexer.GetHeader(fork.HashBlock) == null) { fork = fork.Previous; } this.logger.LogInformation("Reorg detected, going back from '{0}' to '{1}'.", this.walletTip, fork); this.walletManager.RemoveBlocks(fork); this.walletTip = fork; this.logger.LogTrace("Wallet tip set to '{0}'.", this.walletTip); } // The new tip can be ahead or behind the wallet. // If the new tip is ahead we try to bring the wallet up to the new tip. // If the new tip is behind we just check the wallet and the tip are in the same chain. if (newTip.Height > this.walletTip.Height) { ChainedHeader findTip = newTip.FindAncestorOrSelf(this.walletTip); if (findTip == null) { this.logger.LogTrace("(-)[NEW_TIP_AHEAD_NOT_IN_WALLET]"); return; } this.logger.LogTrace("Wallet tip '{0}' is behind the new tip '{1}'.", this.walletTip, newTip); ChainedHeader next = this.walletTip; while (next != newTip) { // While the wallet is catching up the entire node will wait. // If a wallet is recovered to a date in the past. Consensus will stop until the wallet is up to date. // TODO: This code should be replaced with a different approach // Similar to BlockStore the wallet should be standalone and not depend on consensus. // The block should be put in a queue and pushed to the wallet in an async way. // If the wallet is behind it will just read blocks from store (or download in case of a pruned node). next = newTip.GetAncestor(next.Height + 1); Block nextblock = null; int index = 0; while (true) { if (cancellationToken.IsCancellationRequested) { this.logger.LogTrace("(-)[CANCELLATION_REQUESTED]"); return; } nextblock = this.blockStore.GetBlock(next.HashBlock); if (nextblock == null) { // The idea in this abandoning of the loop is to release consensus to push the block. // That will make the block available in the next push from consensus. index++; if (index > 10) { this.logger.LogTrace("(-)[WALLET_CATCHUP_INDEX_MAX]"); return; } // Really ugly hack to let store catch up. // This will block the entire consensus pulling. this.logger.LogWarning("Wallet is behind the best chain and the next block is not found in store."); Thread.Sleep(100); continue; } break; } this.walletTip = next; this.walletManager.ProcessBlock(nextblock, next); } } else { ChainedHeader findTip = this.walletTip.FindAncestorOrSelf(newTip); if (findTip == null) { this.logger.LogTrace("(-)[NEW_TIP_BEHIND_NOT_IN_WALLET]"); return; } this.logger.LogTrace("Wallet tip '{0}' is ahead or equal to the new tip '{1}'.", this.walletTip, newTip); } } else { this.logger.LogTrace("New block follows the previously known block '{0}'.", this.walletTip); } this.walletTip = newTip; this.walletManager.ProcessBlock(block, newTip); }
/// <inheritdoc /> public async Task AnnounceBlocksAsync(List <ChainedHeader> blocksToAnnounce) { Guard.NotNull(blocksToAnnounce, nameof(blocksToAnnounce)); this.logger.LogTrace("({0}.{1}:{2})", nameof(blocksToAnnounce), nameof(blocksToAnnounce.Count), blocksToAnnounce.Count); if (!blocksToAnnounce.Any()) { this.logger.LogTrace("(-)[NO_BLOCKS]"); return; } INetworkPeer peer = this.AttachedPeer; if (peer == null) { this.logger.LogTrace("(-)[NO_PEER]"); return; } bool revertToInv = ((!this.PreferHeaders && (!this.preferHeaderAndIDs || blocksToAnnounce.Count > 1)) || blocksToAnnounce.Count > MAX_BLOCKS_TO_ANNOUNCE); this.logger.LogTrace("Block propagation preferences of the peer '{0}': prefer headers - {1}, prefer headers and IDs - {2}, will{3} revert to 'inv' now.", peer.RemoteSocketEndpoint, this.PreferHeaders, this.preferHeaderAndIDs, revertToInv ? "" : " NOT"); var headers = new List <BlockHeader>(); var inventoryBlockToSend = new List <uint256>(); try { ChainedHeader highestHeader = this.GetBestHeader(); ChainedHeader bestIndex = null; if (!revertToInv) { bool foundStartingHeader = false; // Try to find first chained block that the peer doesn't have, and then add all chained blocks past that one. foreach (ChainedHeader chainedHeader in blocksToAnnounce) { bestIndex = chainedHeader; if (!foundStartingHeader) { this.logger.LogTrace("Checking is the peer '{0}' can connect header '{1}'.", peer.RemoteSocketEndpoint, chainedHeader); // Peer doesn't have a block at the height of our block and with the same hash? if (highestHeader?.FindAncestorOrSelf(chainedHeader) != null) { this.logger.LogTrace("Peer '{0}' already has header '{1}'.", peer.RemoteSocketEndpoint, chainedHeader.Previous); continue; } // Peer doesn't have a block at the height of our block.Previous and with the same hash? if (highestHeader?.FindAncestorOrSelf(chainedHeader.Previous) == null) { // Peer doesn't have this header or the prior one - nothing will connect, so bail out. this.logger.LogTrace("Neither the header nor its previous header found for peer '{0}', reverting to 'inv'.", peer.RemoteSocketEndpoint); revertToInv = true; break; } this.logger.LogTrace("Peer '{0}' can connect header '{1}'.", peer.RemoteSocketEndpoint, chainedHeader.Previous); foundStartingHeader = true; } // If we reached here then it means that we've found starting header. headers.Add(chainedHeader.Header); } } if (!revertToInv && headers.Any()) { if ((headers.Count == 1) && this.preferHeaderAndIDs) { // TODO: } else if (this.PreferHeaders) { if (headers.Count > 1) { this.logger.LogDebug("Sending {0} headers, range {1} - {2}, to peer '{3}'.", headers.Count, headers.First(), headers.Last(), peer.RemoteSocketEndpoint); } else { this.logger.LogDebug("Sending header '{0}' to peer '{1}'.", headers.First(), peer.RemoteSocketEndpoint); } this.lastHeaderSent = bestIndex; await peer.SendMessageAsync(new HeadersPayload(headers.ToArray())).ConfigureAwait(false); this.logger.LogTrace("(-)[SEND_HEADERS_PAYLOAD]"); return; } else { revertToInv = true; } } if (revertToInv) { // If falling back to using an inv, just try to inv the tip. // The last entry in 'blocksToAnnounce' was our tip at some point in the past. if (blocksToAnnounce.Any()) { ChainedHeader chainedHeader = blocksToAnnounce.Last(); if (chainedHeader != null) { if ((highestHeader == null) || (highestHeader.GetAncestor(chainedHeader.Height) == null)) { inventoryBlockToSend.Add(chainedHeader.HashBlock); this.logger.LogDebug("Sending inventory hash '{0}' to peer '{1}'.", chainedHeader.HashBlock, peer.RemoteSocketEndpoint); } } } } if (inventoryBlockToSend.Any()) { await this.SendAsBlockInventoryAsync(peer, inventoryBlockToSend).ConfigureAwait(false); this.logger.LogTrace("(-)[SEND_INVENTORY]"); return; } } catch (OperationCanceledException) { this.logger.LogTrace("(-)[CANCELED_EXCEPTION]"); return; } this.logger.LogTrace("(-)"); }
public async Task ReorgChain_FailsFullValidation_Reconnect_OldChain_Nodes_ConnectedAsync() { using (var builder = NodeBuilder.Create(this)) { var bitcoinNoValidationRulesNetwork = new BitcoinRegTestNoValidationRules(); var minerA = builder.CreateStratisPowNode(this.powNetwork).WithDummyWallet().WithReadyBlockchainData(ReadyBlockchain.BitcoinRegTest10Miner); var minerB = builder.CreateStratisPowNode(bitcoinNoValidationRulesNetwork).NoValidation().WithDummyWallet().Start(); ChainedHeader minerBChainTip = null; bool interceptorsEnabled = false; bool minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain = false; bool minerA_IsConnecting_To_MinerBChain = false; bool minerA_Disconnected_MinerBsChain = false; bool minerA_Reconnected_Its_OwnChain = false; // Configure the interceptor to intercept when Miner A connects Miner B's chain. void interceptorConnect(ChainedHeaderBlock chainedHeaderBlock) { if (!interceptorsEnabled) { return; } if (!minerA_IsConnecting_To_MinerBChain) { if (chainedHeaderBlock.ChainedHeader.Height == 12) { minerA_IsConnecting_To_MinerBChain = minerA.FullNode.ConsensusManager().Tip.HashBlock == minerBChainTip.GetAncestor(12).HashBlock; } return; } if (!minerA_Reconnected_Its_OwnChain) { if (chainedHeaderBlock.ChainedHeader.Height == 14) { minerA_Reconnected_Its_OwnChain = true; } return; } } // Configure the interceptor to intercept when Miner A disconnects Miner B's chain after the reorg. void interceptorDisconnect(ChainedHeaderBlock chainedHeaderBlock) { if (!interceptorsEnabled) { return; } if (!minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain) { if (minerA.FullNode.ConsensusManager().Tip.Height == 10) { minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain = true; } return; } if (!minerA_Disconnected_MinerBsChain) { if (minerA.FullNode.ConsensusManager().Tip.Height == 10) { minerA_Disconnected_MinerBsChain = true; } return; } } minerA.SetConnectInterceptor(interceptorConnect); minerA.SetDisconnectInterceptor(interceptorDisconnect); minerA.Start(); // Miner B syncs with Miner A TestHelper.ConnectAndSync(minerB, minerA); // Disable Miner A from sending blocks to Miner B TestHelper.DisableBlockPropagation(minerA, minerB); // Miner A continues to mine to height 14 TestHelper.MineBlocks(minerA, 4); TestHelper.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 14); Assert.Equal(10, minerB.FullNode.ConsensusManager().Tip.Height); // Enable the interceptors so that they are active during the reorg. interceptorsEnabled = true; // Miner B mines 5 more blocks: // Block 6,7,9,10 = valid // Block 8 = invalid minerBChainTip = await TestHelper.BuildBlocks.OnNode(minerB).Amount(5).Invalid(13, (coreNode, block) => BlockBuilder.InvalidCoinbaseReward(coreNode, block)).BuildAsync(); Assert.Equal(15, minerBChainTip.Height); Assert.Equal(15, minerB.FullNode.ConsensusManager().Tip.Height); // Wait until Miner A disconnected its own chain so that it can connect to // Miner B's longer chain. TestHelper.WaitLoop(() => minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain); // Wait until Miner A has connected Miner B's chain (but failed) TestHelper.WaitLoop(() => minerA_IsConnecting_To_MinerBChain); // Wait until Miner A has disconnected Miner B's invalid chain. TestHelper.WaitLoop(() => minerA_Disconnected_MinerBsChain); // Wait until Miner A has reconnected its own chain. TestHelper.WaitLoop(() => minerA_Reconnected_Its_OwnChain); } }
/// <summary> /// Determines the state of a BIP from the cache and/or the chain header history and the corresponding version bits. /// </summary> /// <param name="indexPrev">The previous header of the chain header to determine the states for.</param> /// <param name="deployment">The deployment to check the state of.</param> /// <returns>The current state of the deployment.</returns> public ThresholdState GetState(ChainedHeader indexPrev, int deployment) { int period = this.consensus.MinerConfirmationWindow; int threshold = this.consensus.RuleChangeActivationThreshold; DateTimeOffset?timeStart = this.consensus.BIP9Deployments[deployment]?.StartTime; DateTimeOffset?timeTimeout = this.consensus.BIP9Deployments[deployment]?.Timeout; // Check if this deployment is always active. if (timeStart == Utils.UnixTimeToDateTime(BIP9DeploymentsParameters.AlwaysActive)) { return(ThresholdState.Active); } // A block's state is always the same as that of the first of its period, so it is computed based on a pindexPrev whose height equals a multiple of nPeriod - 1. if (indexPrev != null) { indexPrev = indexPrev.GetAncestor(indexPrev.Height - ((indexPrev.Height + 1) % period)); } // Walk backwards in steps of nPeriod to find a pindexPrev whose information is known. var vToCompute = new List <ChainedHeader>(); while (!this.ContainsKey(indexPrev?.HashBlock, deployment)) { if (indexPrev.GetMedianTimePast() < timeStart) { // Optimization: don't recompute down further, as we know every earlier block will be before the start time. this.Set(indexPrev?.HashBlock, deployment, ThresholdState.Defined); break; } vToCompute.Add(indexPrev); indexPrev = indexPrev.GetAncestor(indexPrev.Height - period); } // At this point, cache[pindexPrev] is known. this.Assert(this.ContainsKey(indexPrev?.HashBlock, deployment)); ThresholdState state = this.Get(indexPrev?.HashBlock, deployment); // Now walk forward and compute the state of descendants of pindexPrev. while (vToCompute.Count != 0) { ThresholdState stateNext = state; indexPrev = vToCompute[vToCompute.Count - 1]; vToCompute.RemoveAt(vToCompute.Count - 1); switch (state) { case ThresholdState.Defined: { if (indexPrev.GetMedianTimePast() >= timeTimeout) { stateNext = ThresholdState.Failed; } else if (indexPrev.GetMedianTimePast() >= timeStart) { stateNext = ThresholdState.Started; } break; } case ThresholdState.Started: { if (indexPrev.GetMedianTimePast() >= timeTimeout) { stateNext = ThresholdState.Failed; break; } // Counts the "votes" in the confirmation window to determine // whether the rule change activation threshold has been met. ChainedHeader pindexCount = indexPrev; int count = 0; for (int i = 0; i < period; i++) { if (this.Condition(pindexCount, deployment)) { count++; } pindexCount = pindexCount.Previous; } // If the threshold has been met then lock in the BIP activation. if (count >= threshold) { stateNext = ThresholdState.LockedIn; } break; } case ThresholdState.LockedIn: { // Always progresses into ACTIVE. stateNext = ThresholdState.Active; break; } case ThresholdState.Failed: case ThresholdState.Active: { // Nothing happens, these are terminal states. break; } } this.Set(indexPrev?.HashBlock, deployment, state = stateNext); } return(state); }
/// <summary> /// Processes "getblocks" message received from the peer. /// </summary> /// <param name="peer">Peer that sent the message.</param> /// <param name="getBlocksPayload">Payload of "getblocks" message to process.</param> private async Task ProcessGetBlocksAsync(INetworkPeer peer, GetBlocksPayload getBlocksPayload) { this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(peer), peer.RemoteSocketEndpoint, nameof(getBlocksPayload), getBlocksPayload); // We only want to work with blocks that are in the store, // so we first get information about the store's tip. ChainedHeader blockStoreTip = this.chain.GetBlock(this.blockRepository.BlockHash); if (blockStoreTip == null) { this.logger.LogTrace("(-)[REORG]"); return; } this.logger.LogTrace("Block store tip is '{0}'.", blockStoreTip); // Now we want to find the last common block between our chain and the block locator the peer sent us. ChainedHeader chainTip = this.chain.Tip; ChainedHeader forkPoint = null; // Find last common block between our chain and the block locator the peer sent us. while (forkPoint == null) { forkPoint = this.chain.FindFork(getBlocksPayload.BlockLocators.Blocks); if (forkPoint == null) { this.logger.LogTrace("(-)[NO_FORK_POINT]"); return; } // In case of reorg, we just try again, eventually we succeed. if (chainTip.FindAncestorOrSelf(forkPoint) == null) { chainTip = this.chain.Tip; forkPoint = null; } } this.logger.LogTrace("Fork point is '{0}'.", forkPoint); // If block store is lower than the fork point, or it is on different chain, we don't have anything to contribute to this peer at this point. if (blockStoreTip.FindAncestorOrSelf(forkPoint) == null) { this.logger.LogTrace("(-)[FORK_OUTSIDE_STORE]"); return; } // Now we compile a list of blocks we want to send to the peer as inventory vectors. // This is needed as we want to traverse the chain in forward direction. int maxHeight = Math.Min(blockStoreTip.Height, forkPoint.Height + InvPayload.MaxGetBlocksInventorySize); ChainedHeader lastBlock = blockStoreTip.GetAncestor(maxHeight); int headersCount = maxHeight - forkPoint.Height; this.logger.LogTrace("Last block to announce is '{0}', number of blocks to announce is {1}.", lastBlock, headersCount); var headersToAnnounce = new ChainedHeader[headersCount]; for (int i = headersCount - 1; i >= 0; i--) { headersToAnnounce[i] = lastBlock; lastBlock = lastBlock.Previous; } // Now we compile inventory payload and we also consider hash stop given by the peer. bool sendContinuation = true; ChainedHeader lastAddedChainedHeader = null; var inv = new InvPayload(); for (int i = 0; i < headersToAnnounce.Length; i++) { ChainedHeader chainedHeader = headersToAnnounce[i]; if (chainedHeader.HashBlock == getBlocksPayload.HashStop) { this.logger.LogTrace("Hash stop has been reached."); break; } this.logger.LogTrace("Adding block '{0}' to the inventory.", chainedHeader); lastAddedChainedHeader = chainedHeader; inv.Inventory.Add(new InventoryVector(InventoryType.MSG_BLOCK, chainedHeader.HashBlock)); if (chainedHeader.HashBlock == chainTip.HashBlock) { this.logger.LogTrace("Tip of the chain has been reached."); sendContinuation = false; } } int count = inv.Inventory.Count; if (count > 0) { ChainedHeader highestHeader = this.GetBestHeader(); if (highestHeader?.Height < lastAddedChainedHeader.Height) { this.logger.LogTrace("Setting peer's last block sent to '{0}'.", lastAddedChainedHeader); this.lastHeaderSent = lastAddedChainedHeader; // Set last item of the batch (unless we are announcing the tip), which is then used // when the peer sends us "getdata" message. When we detect "getdata" message for this block, // we will send continuation inventory message. This will cause the peer to ask for another batch of blocks. // See ProcessGetDataAsync method. if (sendContinuation) { this.getBlocksBatchLastItemHash = lastAddedChainedHeader.HashBlock; } } this.logger.LogTrace("Sending inventory with {0} block hashes.", count); await peer.SendMessageAsync(inv).ConfigureAwait(false); } else { this.logger.LogTrace("Nothing to send."); } this.logger.LogTrace("(-)"); }
/// <summary> /// Computes the metrics of all BIP9 deployments for a given block. /// </summary> /// <param name="indexPrev">The block at which to compute the metrics.</param> /// <param name="thresholdStates">The current state of each BIP9 deployment.</param> /// <returns>A <see cref="ThresholdStateModel" /> object containg the metrics.</returns> public List <ThresholdStateModel> GetThresholdStateMetrics(ChainedHeader indexPrev, ThresholdState[] thresholdStates) { var thresholdStateModels = new List <ThresholdStateModel>(); ThresholdState[] array = new ThresholdState[this.consensus.BIP9Deployments.Length]; for (int deploymentIndex = 0; deploymentIndex < array.Length; deploymentIndex++) { if (this.consensus.BIP9Deployments[deploymentIndex] == null) { continue; } string deploymentName = this.consensus.BIP9Deployments[deploymentIndex]?.Name; DateTime?timeStart = this.consensus.BIP9Deployments[deploymentIndex]?.StartTime.Date; DateTime?timeTimeout = this.consensus.BIP9Deployments[deploymentIndex]?.Timeout.Date; long threshold = this.consensus.BIP9Deployments[deploymentIndex].Threshold; int votes = 0; int currentHeight = indexPrev.Height + 1; int period = this.consensus.MinerConfirmationWindow; // First ancestor outside last confirmation window. If we haven't reached block height 2016 yet this will be the genesis block. int periodStart = (indexPrev.Height - (currentHeight % period)) > 0 ? (indexPrev.Height - (currentHeight % period)) : 0; ChainedHeader periodStartsHeader = indexPrev.GetAncestor(periodStart); int periodEndsHeight = periodStartsHeader.Height + period; var hexVersions = new Dictionary <string, int>(); int totalBlocks = 0; ChainedHeader headerTemp = indexPrev; while (headerTemp != periodStartsHeader) { if (this.Condition(headerTemp, deploymentIndex)) { votes++; } totalBlocks++; string hexVersion = headerTemp.Header.Version.ToString("X8"); if (!hexVersions.TryGetValue(hexVersion, out int count)) { count = 0; } hexVersions[hexVersion] = count + 1; headerTemp = headerTemp.Previous; } // look in the cache for the hash of the first block an item was deployed var firstSeenHash = this.cache.FirstOrDefault(c => c.Value[deploymentIndex] == ThresholdState.Started); int sinceHeight = 0; if (firstSeenHash.Key != null) { sinceHeight = indexPrev.FindAncestorOrSelf(firstSeenHash.Key).Height; } thresholdStateModels.Add(new ThresholdStateModel() { DeploymentName = deploymentName, DeploymentIndex = deploymentIndex, ConfirmationPeriod = period, Blocks = totalBlocks, Votes = votes, HexVersions = hexVersions, TimeStart = timeStart, TimeTimeOut = timeTimeout, Threshold = threshold, Height = currentHeight, SinceHeight = sinceHeight, PeriodStartHeight = periodStartsHeader.Height, PeriodEndHeight = periodEndsHeight, StateValue = thresholdStates[deploymentIndex], ThresholdState = ((ThresholdState)thresholdStates[deploymentIndex]).ToString() }); } return(thresholdStateModels); }
public Target GetWorkRequired(ChainedHeader chainedHeaderToValidate, XRCConsensus consensus) { // Genesis block. if (chainedHeaderToValidate.Height == 0) { return(consensus.PowLimit2); } var XRCConsensusProtocol = (XRCConsensusProtocol)consensus.ConsensusFactory.Protocol; //hard fork if (chainedHeaderToValidate.Height == XRCConsensusProtocol.PowLimit2Height + 1) { return(consensus.PowLimit); } //hard fork 2 - DigiShield + X11 if (chainedHeaderToValidate.Height > XRCConsensusProtocol.PowDigiShieldX11Height) { return(GetWorkRequiredDigiShield(chainedHeaderToValidate, consensus)); } Target proofOfWorkLimit; // Hard fork to higher difficulty if (chainedHeaderToValidate.Height > XRCConsensusProtocol.PowLimit2Height) { proofOfWorkLimit = consensus.PowLimit; } else { proofOfWorkLimit = consensus.PowLimit2; } ChainedHeader lastBlock = chainedHeaderToValidate.Previous; int height = chainedHeaderToValidate.Height; if (lastBlock == null) { return(proofOfWorkLimit); } long difficultyAdjustmentInterval = GetDifficultyAdjustmentInterval(consensus); // Only change once per interval. if ((height) % difficultyAdjustmentInterval != 0) { if (consensus.PowAllowMinDifficultyBlocks) { // Special difficulty rule for testnet: // If the new block's timestamp is more than 2* 10 minutes // then allow mining of a min-difficulty block. if (chainedHeaderToValidate.Header.BlockTime > (lastBlock.Header.BlockTime + TimeSpan.FromTicks(consensus.TargetSpacing.Ticks * 2))) { return(proofOfWorkLimit); } // Return the last non-special-min-difficulty-rules-block. ChainedHeader chainedHeader = lastBlock; while ((chainedHeader.Previous != null) && ((chainedHeader.Height % difficultyAdjustmentInterval) != 0) && (chainedHeader.Header.Bits == proofOfWorkLimit)) { chainedHeader = chainedHeader.Previous; } return(chainedHeader.Header.Bits); } return(lastBlock.Header.Bits); } // Go back by what we want to be 14 days worth of blocks. long pastHeight = lastBlock.Height - (difficultyAdjustmentInterval - 1); ChainedHeader firstChainedHeader = chainedHeaderToValidate.GetAncestor((int)pastHeight); if (firstChainedHeader == null) { throw new NotSupportedException("Can only calculate work of a full chain"); } if (consensus.PowNoRetargeting) { return(lastBlock.Header.Bits); } // Limit adjustment step. TimeSpan actualTimespan = lastBlock.Header.BlockTime - firstChainedHeader.Header.BlockTime; if (actualTimespan < TimeSpan.FromTicks(consensus.TargetTimespan.Ticks / 4)) { actualTimespan = TimeSpan.FromTicks(consensus.TargetTimespan.Ticks / 4); } if (actualTimespan > TimeSpan.FromTicks(consensus.TargetTimespan.Ticks * 4)) { actualTimespan = TimeSpan.FromTicks(consensus.TargetTimespan.Ticks * 4); } // Retarget. BigInteger newTarget = lastBlock.Header.Bits.ToBigInteger(); newTarget = newTarget.Multiply(BigInteger.ValueOf((long)actualTimespan.TotalSeconds)); newTarget = newTarget.Divide(BigInteger.ValueOf((long)consensus.TargetTimespan.TotalSeconds)); var finalTarget = new Target(newTarget); if (finalTarget > proofOfWorkLimit) { finalTarget = proofOfWorkLimit; } return(finalTarget); }
/// <summary> /// Check if transaction will be BIP 68 final in the next block to be created. /// Simulates calling SequenceLocks() with data from the tip of the current active chain. /// Optionally stores in LockPoints the resulting height and time calculated and the hash /// of the block needed for calculation or skips the calculation and uses the LockPoints /// passed in for evaluation. /// The LockPoints should not be considered valid if CheckSequenceLocks returns false. /// See consensus/consensus.h for flag definitions. /// </summary> /// <param name="network">The blockchain network.</param> /// <param name="tip">Tip of the chain.</param> /// <param name="context">Validation context for the memory pool.</param> /// <param name="flags">Transaction lock time flags.</param> /// <param name="lp">Optional- existing lock points to use, and update during evaluation.</param> /// <param name="useExistingLockPoints">Whether to use the existing lock points during evaluation.</param> /// <returns>Whether sequence lock validated.</returns> /// <seealso cref="SequenceLock.Evaluate(ChainedHeader)"/> public static bool CheckSequenceLocks(Network network, ChainedHeader tip, MempoolValidationContext context, Transaction.LockTimeFlags flags, LockPoints lp = null, bool useExistingLockPoints = false) { Block dummyBlock = network.Consensus.ConsensusFactory.CreateBlock(); dummyBlock.Header.HashPrevBlock = tip.HashBlock; var index = new ChainedHeader(dummyBlock.Header, dummyBlock.GetHash(), tip); // CheckSequenceLocks() uses chainActive.Height()+1 to evaluate // height based locks because when SequenceLocks() is called within // ConnectBlock(), the height of the block *being* // evaluated is what is used. // Thus if we want to know if a transaction can be part of the // *next* block, we need to use one more than chainActive.Height() SequenceLock lockPair; if (useExistingLockPoints) { Guard.Assert(lp != null); lockPair = new SequenceLock(lp.Height, lp.Time); } else { // pcoinsTip contains the UTXO set for chainActive.Tip() var prevheights = new List <int>(); foreach (TxIn txin in context.Transaction.Inputs) { UnspentOutputs coins = context.View.GetCoins(txin.PrevOut.Hash); if (coins == null) { return(false); } if (coins.Height == TxMempool.MempoolHeight) { // Assume all mempool transaction confirm in the next block prevheights.Add(tip.Height + 1); } else { prevheights.Add((int)coins.Height); } } lockPair = context.Transaction.CalculateSequenceLocks(prevheights.ToArray(), index, flags); if (lp != null) { lp.Height = lockPair.MinHeight; lp.Time = lockPair.MinTime.ToUnixTimeMilliseconds(); // Also store the hash of the block with the highest height of // all the blocks which have sequence locked prevouts. // This hash needs to still be on the chain // for these LockPoint calculations to be valid // Note: It is impossible to correctly calculate a maxInputBlock // if any of the sequence locked inputs depend on unconfirmed txs, // except in the special case where the relative lock time/height // is 0, which is equivalent to no sequence lock. Since we assume // input height of tip+1 for mempool txs and test the resulting // lockPair from CalculateSequenceLocks against tip+1. We know // EvaluateSequenceLocks will fail if there was a non-zero sequence // lock on a mempool input, so we can use the return value of // CheckSequenceLocks to indicate the LockPoints validity int maxInputHeight = 0; foreach (int height in prevheights) { // Can ignore mempool inputs since we'll fail if they had non-zero locks if (height != tip.Height + 1) { maxInputHeight = Math.Max(maxInputHeight, height); } } lp.MaxInputBlock = tip.GetAncestor(maxInputHeight); } } return(lockPair.Evaluate(index)); }
/// <summary> /// Computes the metrics of all BIP9 deployments for a given block. /// </summary> /// <param name="indexPrev">The block at which to compute the metrics.</param> /// <param name="thresholdStates">The current state of each BIP9 deployment.</param> /// <returns>A <see cref="ThresholdStateModel" /> object containg the metrics.</returns> public List <ThresholdStateModel> GetThresholdStateMetrics(ChainedHeader indexPrev, ThresholdState[] thresholdStates) { var thresholdStateModels = new List <ThresholdStateModel>(); ThresholdState[] array = new ThresholdState[this.consensus.BIP9Deployments.Length]; for (int deploymentIndex = 0; deploymentIndex < array.Length; deploymentIndex++) { if (this.consensus.BIP9Deployments[deploymentIndex] == null) { continue; } DateTime?timeStart = this.consensus.BIP9Deployments[deploymentIndex]?.StartTime.Date; DateTime?timeTimeout = this.consensus.BIP9Deployments[deploymentIndex]?.Timeout.Date; int threshold = this.consensus.RuleChangeActivationThreshold; int votes = 0; int currentHeight = indexPrev.Height + 1; int period = this.consensus.MinerConfirmationWindow; // First ancestor outside last confirmation window. ChainedHeader periodStartsHeader = indexPrev.GetAncestor(indexPrev.Height - (currentHeight % period)); int periodEndsHeight = periodStartsHeader.Height + period; var hexVersions = new Dictionary <string, int>(); int totalBlocks = 0; ChainedHeader headerTemp = indexPrev; while (headerTemp != periodStartsHeader) { if (this.Condition(headerTemp, deploymentIndex)) { votes++; } totalBlocks++; string hexVersion = headerTemp.Header.Version.ToString("X8"); if (!hexVersions.TryGetValue(hexVersion, out int count)) { count = 0; } hexVersions[hexVersion] = count + 1; headerTemp = headerTemp.Previous; } thresholdStateModels.Add(new ThresholdStateModel() { DeploymentIndex = deploymentIndex, ConfirmationPeriod = period, Blocks = totalBlocks, Votes = votes, HexVersions = hexVersions, TimeStart = timeStart, TimeTimeOut = timeTimeout, Threshold = threshold, Height = currentHeight, PeriodStartHeight = periodStartsHeader.Height, PeriodEndHeight = periodEndsHeight, StateValue = thresholdStates[deploymentIndex], ThresholdState = ((ThresholdState)thresholdStates[deploymentIndex]).ToString() }); } return(thresholdStateModels); }
/// <inheritdoc /> public async Task <Result <List <MaturedBlockDepositsModel> > > GetMaturedDepositsAsync(int blockHeight, int maxBlocks, int maxDeposits = int.MaxValue) { ChainedHeader consensusTip = this.consensusManager.Tip; if (consensusTip == null) { return(Result <List <MaturedBlockDepositsModel> > .Fail("Not ready to provide blocks.")); } int matureTipHeight = (consensusTip.Height - (int)this.depositExtractor.MinimumDepositConfirmations); if (blockHeight > matureTipHeight) { // We need to return a Result type here to explicitly indicate failure and the reason for failure. // This is an expected condition so we can avoid throwing an exception here. return(Result <List <MaturedBlockDepositsModel> > .Fail($"Block height {blockHeight} submitted is not mature enough. Blocks less than a height of {matureTipHeight} can be processed.")); } var maturedBlocks = new List <MaturedBlockDepositsModel>(); // Half of the timeout. We will also need time to convert it to json. int maxTimeCollectionCanTakeMs = RestApiClientBase.TimeoutMs / 2; var cancellation = new CancellationTokenSource(maxTimeCollectionCanTakeMs); int maxBlockHeight = Math.Min(matureTipHeight, blockHeight + maxBlocks - 1); var headers = new List <ChainedHeader>(); ChainedHeader header = consensusTip.GetAncestor(maxBlockHeight); for (int i = maxBlockHeight; i >= blockHeight; i--) { headers.Add(header); header = header.Previous; } headers.Reverse(); int numDeposits = 0; for (int ndx = 0; ndx < headers.Count; ndx += 100) { List <ChainedHeader> currentHeaders = headers.GetRange(ndx, Math.Min(100, headers.Count - ndx)); List <uint256> hashes = currentHeaders.Select(h => h.HashBlock).ToList(); ChainedHeaderBlock[] blocks = this.consensusManager.GetBlockData(hashes); foreach (ChainedHeaderBlock chainedHeaderBlock in blocks) { if (chainedHeaderBlock?.Block?.Transactions == null) { this.logger.LogDebug("Unexpected null data. Send what we've collected."); return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks)); } MaturedBlockDepositsModel maturedBlockDeposits = this.depositExtractor.ExtractBlockDeposits(chainedHeaderBlock); maturedBlocks.Add(maturedBlockDeposits); numDeposits += maturedBlockDeposits.Deposits?.Count ?? 0; if (maturedBlocks.Count >= maxBlocks || numDeposits >= maxDeposits) { return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks)); } if (cancellation.IsCancellationRequested) { this.logger.LogDebug("Stop matured blocks collection because it's taking too long. Send what we've collected."); return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks)); } } } return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks)); }
/// <inheritdoc /> public async Task <List <MaturedBlockDepositsModel> > GetMaturedDepositsAsync(int blockHeight, int maxBlocks) { ChainedHeader consensusTip = this.consensusManager.Tip; int matureTipHeight = (consensusTip.Height - (int)this.depositExtractor.MinimumDepositConfirmations); if (blockHeight > matureTipHeight) { throw new InvalidOperationException($"Block height {blockHeight} submitted is not mature enough. Blocks less than a height of {matureTipHeight} can be processed."); } // Cache clean-up. lock (this.depositCache) { // The requested height gives away the fact that the peer is probably no longer interested in cached entries below that height. // Keep an additional 1,000 blocks anyway in case there are some parallel request that are still executing for lower heights. foreach (int i in this.depositCache.Where(d => d.Key < (blockHeight - 1000)).Select(d => d.Key).ToArray()) { this.depositCache.Remove(i); } } var maturedBlocks = new List <MaturedBlockDepositsModel>(); // Don't spend to much time that the requester may give up. DateTime deadLine = DateTime.Now.AddSeconds(30); for (int i = blockHeight; (i <= matureTipHeight) && (i < blockHeight + maxBlocks); i++) { MaturedBlockDepositsModel maturedBlockDeposits = null; // First try the cache. lock (this.depositCache) { this.depositCache.TryGetValue(i, out maturedBlockDeposits); } // If not in cache.. if (maturedBlockDeposits == null) { ChainedHeader currentHeader = consensusTip.GetAncestor(i); ChainedHeaderBlock block = await this.consensusManager.GetBlockDataAsync(currentHeader.HashBlock).ConfigureAwait(false); maturedBlockDeposits = this.depositExtractor.ExtractBlockDeposits(block); if (maturedBlockDeposits == null) { throw new InvalidOperationException($"Unable to get deposits for block at height {currentHeader.Height}"); } // Save this so that we don't need to scan the block again. lock (this.depositCache) { this.depositCache[i] = maturedBlockDeposits; } } maturedBlocks.Add(maturedBlockDeposits); if (DateTime.Now >= deadLine) { break; } } return(maturedBlocks); }
/// <inheritdoc /> public override async Task InitializeAsync() { // TODO rewrite chain starting logic. Tips manager should be used. await this.StartChainAsync().ConfigureAwait(false); ChainedHeader initializedAt = this.chainIndexer.Tip; if (this.provenBlockHeaderStore != null) { // If we find at this point that proven header store is behind chain we can rewind chain (this will cause a ripple effect and rewind block store and consensus) // This problem should go away once we implement a component to keep all tips up to date // https://github.com/stratisproject/StratisBitcoinFullNode/issues/2503 initializedAt = await this.provenBlockHeaderStore.InitializeAsync(this.chainIndexer.Tip); } var rewindHeight = this.nodeSettings.ConfigReader.GetOrDefault(RewindFlag, -1); if (rewindHeight >= 0) { if (rewindHeight < initializedAt.Height) { // Ensure that we don't try to rewind further than the coin view is capable of doing. var utxoSet = ((dynamic)this.consensusRules).UtxoSet; var coinDatabase = ((dynamic)utxoSet).ICoindb; ((dynamic)coinDatabase).Initialize(initializedAt); int minRewindHeight = ((dynamic)coinDatabase).GetMinRewindHeight(); ((dynamic)coinDatabase).Dispose(); if (minRewindHeight == -1 || rewindHeight < minRewindHeight) { this.logger.LogWarning($"Can't rewind below block at height {minRewindHeight}. Rewind ignored."); } else { initializedAt = initializedAt.GetAncestor(rewindHeight); this.logger.LogInformation($"Rewinding to block at height {rewindHeight}."); } } else { this.logger.LogWarning($"Can't rewind to block at height {rewindHeight}. Rewind ignored."); } SetRewindFlag(this.nodeSettings, -1); } if (this.chainIndexer.Tip.Height != initializedAt.Height) { this.chainIndexer.Initialize(initializedAt); } NetworkPeerConnectionParameters connectionParameters = this.connectionManager.Parameters; connectionParameters.IsRelay = this.connectionManager.ConnectionSettings.RelayTxes; connectionParameters.TemplateBehaviors.Add(new PingPongBehavior()); connectionParameters.TemplateBehaviors.Add(new EnforcePeerVersionCheckBehavior(this.chainIndexer, this.nodeSettings, this.network, this.loggerFactory)); connectionParameters.TemplateBehaviors.Add(new ConsensusManagerBehavior(this.chainIndexer, this.initialBlockDownloadState, this.consensusManager, this.peerBanning, this.loggerFactory)); // TODO: Once a proper rate limiting strategy has been implemented, this check will be removed. if (!this.network.IsRegTest()) { connectionParameters.TemplateBehaviors.Add(new RateLimitingBehavior(this.dateTimeProvider, this.loggerFactory, this.peerBanning)); } connectionParameters.TemplateBehaviors.Add(new PeerBanningBehavior(this.loggerFactory, this.peerBanning, this.nodeSettings)); connectionParameters.TemplateBehaviors.Add(new BlockPullerBehavior(this.blockPuller, this.initialBlockDownloadState, this.dateTimeProvider, this.loggerFactory)); connectionParameters.TemplateBehaviors.Add(new ConnectionManagerBehavior(this.connectionManager, this.loggerFactory)); this.StartAddressManager(connectionParameters); if (this.connectionManager.ConnectionSettings.SyncTimeEnabled) { connectionParameters.TemplateBehaviors.Add(new TimeSyncBehavior(this.timeSyncBehaviorState, this.dateTimeProvider, this.loggerFactory)); } else { this.logger.LogDebug("Time synchronization with peers is disabled."); } // Block store must be initialized before consensus manager. // This may be a temporary solution until a better way is found to solve this dependency. this.blockStore.Initialize(); // The finalized repository needs to be initialized before the rules engine in case the // node shutdown unexpectedly and the finalized block info needs to be reset. this.finalizedBlockInfoRepository.Initialize(this.chainIndexer.Tip); this.consensusRules.Initialize(this.chainIndexer.Tip); await this.consensusManager.InitializeAsync(this.chainIndexer.Tip).ConfigureAwait(false); this.chainState.ConsensusTip = this.consensusManager.Tip; this.nodeStats.RegisterStats(sb => sb.Append(this.asyncProvider.GetStatistics(!this.nodeSettings.LogSettings.DebugArgs.Any())), StatsType.Component, this.GetType().Name, 100); // TODO: Should this always be called? ((IBlockStoreQueue)this.blockStore).ReindexChain(this.consensusManager, this.nodeLifetime.ApplicationStopping); }