public void ValidateTransaction(Chain newChain, ValidatableTx validatableTx) { if (ValidateTransactionAction == null) coreRules.ValidateTransaction(newChain, validatableTx); else ValidateTransactionAction(newChain, validatableTx); }
public void PostValidateBlock(Chain newChain, object finalTally) { if (PreValidateBlockAction == null) coreRules.PostValidateBlock(newChain, finalTally); else PostValidateBlockAction(newChain, finalTally); }
public void ValidationTransactionScript(Chain newChain, BlockTx tx, TxInput txInput, int txInputIndex, PrevTxOutput prevTxOutput) { if (ValidationTransactionScriptAction == null) coreRules.ValidationTransactionScript(newChain, tx, txInput, txInputIndex, prevTxOutput); else ValidationTransactionScriptAction(newChain, tx, txInput, txInputIndex, prevTxOutput); }
public void PreValidateBlock(Chain newChain) { if (PreValidateBlockAction == null) coreRules.PreValidateBlock(newChain); else PreValidateBlockAction(newChain); }
public static async Task ValidateBlockAsync(ICoreStorage coreStorage, ICoreRules rules, Chain newChain, ISourceBlock<ValidatableTx> validatableTxes, CancellationToken cancelToken = default(CancellationToken)) { // tally transactions object finalTally = null; var txTallier = new TransformBlock<ValidatableTx, ValidatableTx>( validatableTx => { var runningTally = finalTally; rules.TallyTransaction(newChain, validatableTx, ref runningTally); finalTally = runningTally; return validatableTx; }); validatableTxes.LinkTo(txTallier, new DataflowLinkOptions { PropagateCompletion = true }); // validate transactions var txValidator = InitTxValidator(rules, newChain, cancelToken); // begin feeding the tx validator txTallier.LinkTo(txValidator, new DataflowLinkOptions { PropagateCompletion = true }); // validate scripts var scriptValidator = InitScriptValidator(rules, newChain, cancelToken); // begin feeding the script validator txValidator.LinkTo(scriptValidator, new DataflowLinkOptions { PropagateCompletion = true }); //TODO await PipelineCompletion.Create( new Task[] { }, new IDataflowBlock[] { validatableTxes, txTallier, txValidator, scriptValidator }); // validate overall block rules.PostValidateBlock(newChain, finalTally); }
public void PreValidateBlock(Chain newChain) { var chainedHeader = newChain.LastBlock; // calculate the next required target var requiredBits = GetRequiredNextBits(newChain); // validate required target var blockTarget = chainedHeader.BlockHeader.CalculateTarget(); if (blockTarget > ChainParams.HighestTarget) throw new ValidationException(chainedHeader.Hash); // validate block's target against the required target if (chainedHeader.Bits != requiredBits) { throw new ValidationException(chainedHeader.Hash, $"Failing block {chainedHeader.Hash} at height {chainedHeader.Height}: Block bits {chainedHeader.Bits} did not match required bits of {requiredBits}"); } // validate block's proof of work against its stated target if (chainedHeader.Hash > blockTarget) { throw new ValidationException(chainedHeader.Hash, $"Failing block {chainedHeader.Hash} at height {chainedHeader.Height}: Block did not match its own target of {blockTarget}"); } //TODO // calculate adjusted time var adjustedTime = DateTimeOffset.Now; // calculate max block time var maxBlockTime = adjustedTime + TimeSpan.FromHours(2); // verify max block time var blockTime = chainedHeader.Time; if (blockTime > maxBlockTime) throw new ValidationException(chainedHeader.Hash); // ensure timestamp is greater than the median timestamp of the previous 11 blocks if (chainedHeader.Height > 0) { var medianTimeSpan = Math.Min(11, newChain.Blocks.Count - 1); var prevHeaderTimes = newChain.Blocks.GetRange(newChain.Blocks.Count - 1 - medianTimeSpan, medianTimeSpan) //TODO pull tester doesn't fail if the sort step is missed .OrderBy(x => x.Time).ToList(); var medianPrevHeaderTime = GetMedianPrevHeaderTime(newChain, chainedHeader.Height); if (blockTime <= medianPrevHeaderTime) { throw new ValidationException(chainedHeader.Hash, $"Failing block {chainedHeader.Hash} at height {chainedHeader.Height}: Block's time of {blockTime} must be greater than past median time of {medianPrevHeaderTime}"); } } //TODO: ContextualCheckBlockHeader nVersion checks }
public void TallyTransaction(Chain newChain, ValidatableTx validatableTx, ref object runningTally) { if (TallyTransactionFunc == null) coreRules.TallyTransaction(newChain, validatableTx, ref runningTally); else { runningTally = TallyTransactionFunc(newChain, validatableTx, runningTally); } }
private static ActionBlock<Tuple<ValidatableTx, int>> InitScriptValidator(ICoreRules rules, Chain newChain, CancellationToken cancelToken) { return new ActionBlock<Tuple<ValidatableTx, int>>( tuple => { var validatableTx = tuple.Item1; var inputIndex = tuple.Item2; var tx = validatableTx.Transaction; var txInput = tx.Inputs[inputIndex]; var prevTxOutputs = validatableTx.PrevTxOutputs[inputIndex]; rules.ValidationTransactionScript(newChain, validatableTx.BlockTx, txInput, inputIndex, prevTxOutputs); }, new ExecutionDataflowBlockOptions { CancellationToken = cancelToken, MaxDegreeOfParallelism = Environment.ProcessorCount }); }
private static TransformManyBlock<ValidatableTx, Tuple<ValidatableTx, int>> InitTxValidator(ICoreRules rules, Chain newChain, CancellationToken cancelToken) { return new TransformManyBlock<ValidatableTx, Tuple<ValidatableTx, int>>( validatableTx => { rules.ValidateTransaction(newChain, validatableTx); if (!rules.IgnoreScripts && !validatableTx.IsCoinbase) { var tx = validatableTx.Transaction; var scripts = new Tuple<ValidatableTx, int>[tx.Inputs.Length]; for (var i = 0; i < tx.Inputs.Length; i++) scripts[i] = Tuple.Create(validatableTx, i); return scripts; } else return new Tuple<ValidatableTx, int>[0]; }, new ExecutionDataflowBlockOptions { CancellationToken = cancelToken, MaxDegreeOfParallelism = Environment.ProcessorCount }); }
public ChainState(Chain chain, IStorageManager storageManager) { CursorCount = Environment.ProcessorCount; Chain = chain; // create a cache of cursors that are in an open snapshot transaction with the current chain state var success = false; this.cursorCache = new DisposableCache<DisposeHandle<IChainStateCursor>>(CursorCount); try { for (var i = 0; i < this.cursorCache.Capacity; i++) { // open the cursor var handle = storageManager.OpenChainStateCursor(); var cursor = handle.Item; // cache the cursor // this must be done before beginning the transaction as caching will rollback any transactions this.cursorCache.CacheItem(handle); // begin transaction to take the snapshot cursor.BeginTransaction(readOnly: true); // verify the chain state matches the expected chain var chainTip = cursor.ChainTip; if (chainTip?.Hash != chain.LastBlock?.Hash) throw new ChainStateOutOfSyncException(chain.LastBlock, chainTip); } success = true; } finally { // ensure any opened cursors are cleaned up on an error if (!success) this.cursorCache.Dispose(); } }
public void ValidationTransactionScript(Chain newChain, BlockTx tx, TxInput txInput, int txInputIndex, PrevTxOutput prevTxOutput) { var chainedHeader = newChain.LastBlock; // BIP16 didn't become active until Apr 1 2012 var nBIP16SwitchTime = DateTimeOffset.FromUnixTimeSeconds(1333238400U); var strictPayToScriptHash = chainedHeader.Time >= nBIP16SwitchTime; var flags = strictPayToScriptHash ? verify_flags_type.verify_flags_p2sh : verify_flags_type.verify_flags_none; // Start enforcing the DERSIG (BIP66) rules, for block.nVersion=3 blocks, // when 75% of the network has upgraded: if (chainedHeader.Version >= 3 && IsSuperMajority(3, newChain, ChainParams.MajorityEnforceBlockUpgrade)) { flags |= verify_flags_type.verify_flags_dersig; } // Start enforcing CHECKLOCKTIMEVERIFY, (BIP65) for block.nVersion=4 // blocks, when 75% of the network has upgraded: if (chainedHeader.Version >= 4 && IsSuperMajority(4, newChain, ChainParams.MajorityEnforceBlockUpgrade)) { flags |= verify_flags_type.verify_flags_checklocktimeverify; } var result = LibbitcoinConsensus.VerifyScript( tx.TxBytes, prevTxOutput.ScriptPublicKey, txInputIndex, flags); if (!result) { logger.Debug($"Script did not pass in block: {chainedHeader.Hash}, tx: {tx.Index}, {tx.Hash}, input: {txInputIndex}"); throw new ValidationException(chainedHeader.Hash); } }
private DateTimeOffset GetMedianPrevHeaderTime(Chain chain, int height) { if (height == 0) return DateTimeOffset.FromUnixTimeSeconds(0); var medianTimeSpan = Math.Min(11, height); var prevHeaderTimes = chain.Blocks.GetRange(height - medianTimeSpan, medianTimeSpan) //TODO pull tester doesn't fail if the sort step is missed .OrderBy(x => x.Time).ToList(); return prevHeaderTimes[prevHeaderTimes.Count / 2].Time; }
private void UpdateTargetChain() { using (updatedTracker.TryUpdate(staleAction: NotifyWork)) { try { var targetBlockLocal = this.targetBlock; var targetChainLocal = this.targetChain; if (targetBlockLocal != null && targetBlockLocal.Hash != targetChainLocal?.LastBlock.Hash) { var newTargetChain = targetChainLocal?.ToBuilder() ?? new ChainBuilder(Chain.CreateForGenesisBlock(this.rules.GenesisChainedHeader)); var deltaBlockPath = new BlockchainWalker().GetBlockchainPath(newTargetChain.LastBlock, targetBlockLocal, blockHash => this.coreStorage.GetChainedHeader(blockHash)); foreach (var rewindBlock in deltaBlockPath.RewindBlocks) { newTargetChain.RemoveBlock(rewindBlock); } foreach (var advanceBlock in deltaBlockPath.AdvanceBlocks) { newTargetChain.AddBlock(advanceBlock); } logger.Debug($"Winning chained block {newTargetChain.LastBlock.Hash} at height {newTargetChain.Height}, total work: {newTargetChain.LastBlock.TotalWork:X}"); this.targetChain = newTargetChain.ToImmutable(); this.OnTargetChainChanged?.Invoke(); } } catch (MissingDataException) { } finally { inittedEvent.Set(); } } }
public bool TryReadChain(UInt256 blockHash, out Chain chain) { return Chain.TryReadChain(blockHash, out chain, headerHash => { ChainedHeader chainedHeader; TryGetChainedHeader(headerHash, out chainedHeader); return chainedHeader; }); }
//TODO this should mark any blocks chained on top as invalid public void MarkBlockInvalid(UInt256 blockHash, Chain targetChain) { var invalidatedBlocks = new List<UInt256>(); try { this.blockStorage.Value.MarkBlockInvalid(blockHash); invalidatedBlocks.Add(blockHash); // mark any blocks further in the target chain as invalid ChainedHeader invalidBlock; if (targetChain.BlocksByHash.TryGetValue(blockHash, out invalidBlock)) { for (var height = invalidBlock.Height; height <= targetChain.Height; height++) { invalidBlock = targetChain.Blocks[height]; this.blockStorage.Value.MarkBlockInvalid(invalidBlock.Hash); invalidatedBlocks.Add(blockHash); } } } finally { foreach (var invalidatedBlock in invalidatedBlocks) BlockInvalidated?.Invoke(invalidatedBlock); } }
public void TallyTransaction(Chain newChain, ValidatableTx validatableTx, ref object runningTally) { var chainedHeader = newChain.LastBlock; if (runningTally == null) { var medianPrevHeaderTime = GetMedianPrevHeaderTime(newChain, chainedHeader.Height); var lockTimeFlags = 0; // TODO why is this used here? var lockTimeCutoff = ((lockTimeFlags & LOCKTIME_MEDIAN_TIME_PAST) != 0) ? GetMedianPrevHeaderTime(newChain, chainedHeader.Height).ToUnixTimeSeconds() : chainedHeader.Time.ToUnixTimeSeconds(); runningTally = new BlockTally { BlockSize = 80, LockTimeCutoff = lockTimeCutoff }; } var blockTally = (BlockTally)runningTally; var tx = validatableTx.Transaction; var txIndex = validatableTx.Index; // BIP16 didn't become active until Apr 1 2012 var nBIP16SwitchTime = DateTimeOffset.FromUnixTimeSeconds(1333238400U); var strictPayToScriptHash = chainedHeader.Time >= nBIP16SwitchTime; // first transaction must be coinbase if (validatableTx.Index == 0 && !tx.IsCoinbase) throw new ValidationException(chainedHeader.Hash); // all other transactions must not be coinbase else if (validatableTx.Index > 0 && tx.IsCoinbase) throw new ValidationException(chainedHeader.Hash); // must have inputs else if (tx.Inputs.Length == 0) throw new ValidationException(chainedHeader.Hash); // must have outputs else if (tx.Outputs.Length == 0) throw new ValidationException(chainedHeader.Hash); // coinbase scriptSignature length must be >= 2 && <= 100 else if (tx.IsCoinbase && (tx.Inputs[0].ScriptSignature.Length < 2 || tx.Inputs[0].ScriptSignature.Length > 100)) throw new ValidationException(chainedHeader.Hash); // all transactions must be finalized else if (!IsFinal(tx, chainedHeader.Height, blockTally.LockTimeCutoff)) throw new ValidationException(chainedHeader.Hash); // Enforce block.nVersion=2 rule that the coinbase starts with serialized block height // if 750 of the last 1,000 blocks are version 2 or greater (51/100 if testnet): if (tx.IsCoinbase && chainedHeader.Version >= 2 && IsSuperMajority(2, newChain, ChainParams.MajorityEnforceBlockUpgrade)) { var requiredScript = GetPushInt64Script(chainedHeader.Height); var actualScript = tx.Inputs[0].ScriptSignature; if (actualScript.Length < requiredScript.Length || !actualScript.Take(requiredScript.Length).SequenceEqual(requiredScript)) { throw new ValidationException(chainedHeader.Hash); } } // running input/output value tallies var txTotalInputValue = 0UL; var txTotalOutputValue = 0UL; var txSigOpCount = 0; // validate & tally inputs for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; if (!tx.IsCoinbase) { var prevOutput = validatableTx.PrevTxOutputs[inputIndex]; // if spending a coinbase, it must be mature if (prevOutput.IsCoinbase && chainedHeader.Height - prevOutput.BlockHeight < COINBASE_MATURITY) { throw new ValidationException(chainedHeader.Hash); } // non-coinbase txes must not have coinbase prev tx output key (txHash: 0, outputIndex: -1) if (input.PrevTxOutputKey.TxOutputIndex == uint.MaxValue && input.PrevTxOutputKey.TxHash == UInt256.Zero) throw new ValidationException(chainedHeader.Hash); // tally txTotalInputValue += prevOutput.Value; } // tally txSigOpCount += CountLegacySigOps(input.ScriptSignature, accurate: false); if (!tx.IsCoinbase && strictPayToScriptHash) txSigOpCount += CountP2SHSigOps(validatableTx, inputIndex); } // validate & tally outputs for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++) { var output = tx.Outputs[outputIndex]; // must not have any negative money value outputs if (unchecked((long)output.Value) < 0) throw new ValidationException(chainedHeader.Hash); // must not have any outputs with a value greater than max money else if (output.Value > MAX_MONEY) throw new ValidationException(chainedHeader.Hash); // tally if (!tx.IsCoinbase) txTotalOutputValue += output.Value; txSigOpCount += CountLegacySigOps(output.ScriptPublicKey, accurate: false); } // must not have a total output value greater than max money if (txTotalOutputValue > MAX_MONEY) throw new ValidationException(chainedHeader.Hash); // validate non-coinbase fees long txFeeValue; if (!validatableTx.IsCoinbase) { // ensure that output amount isn't greater than input amount if (txTotalOutputValue > txTotalInputValue) throw new ValidationException(chainedHeader.Hash, $"Failing tx {tx.Hash}: Transaction output value is greater than input value"); // calculate fee value (unspent amount) txFeeValue = (long)txTotalInputValue - (long)txTotalOutputValue; Debug.Assert(txFeeValue >= 0); } else txFeeValue = 0; // block tallies if (validatableTx.IsCoinbase) blockTally.CoinbaseTx = validatableTx; blockTally.TxCount++; blockTally.TotalFees += txFeeValue; blockTally.TotalSigOpCount += txSigOpCount; // re-encode transaction for block size calculation so it is optimal length blockTally.BlockSize += DataEncoder.EncodeTransaction(validatableTx.Transaction).TxBytes.Length; //TODO if (blockTally.TotalSigOpCount > MAX_BLOCK_SIGOPS) throw new ValidationException(chainedHeader.Hash); //TODO else if (blockTally.BlockSize + DataEncoder.VarIntSize((uint)blockTally.TxCount) > MAX_BLOCK_SIZE) throw new ValidationException(chainedHeader.Hash); // all validation has passed }
public static bool TryReadChain(UInt256 blockHash, out Chain chain, Func<UInt256, ChainedHeader> getChainedHeader) { // return an empty chain for null blockHash // when retrieving a chain by its tip, a null tip represents an empty chain if (blockHash == null) { chain = new ChainBuilder().ToImmutable(); return true; } var retrievedHeaders = new List<ChainedHeader>(); var chainedHeader = getChainedHeader(blockHash); if (chainedHeader != null) { var expectedHeight = chainedHeader.Height; do { if (chainedHeader.Height != expectedHeight) { chain = default(Chain); return false; } retrievedHeaders.Add(chainedHeader); expectedHeight--; } while (expectedHeight >= 0 && chainedHeader.PreviousBlockHash != chainedHeader.Hash && (chainedHeader = getChainedHeader(chainedHeader.PreviousBlockHash)) != null); if (retrievedHeaders.Last().Height != 0) { chain = default(Chain); return false; } var chainBuilder = new ChainBuilder(); for (var i = retrievedHeaders.Count - 1; i >= 0; i--) chainBuilder.AddBlock(retrievedHeaders[i]); chain = chainBuilder.ToImmutable(); return true; } else { chain = default(Chain); return false; } }
private bool IsSuperMajority(int minVersion, Chain newChain, int requiredCount) { if (newChain.Height == 0) return false; var count = 0; var metVersionCount = 0; for (var i = newChain.Height - 1; i >= 0 && count < ChainParams.MajorityWindow; i--) { if (newChain.Blocks[i].Version >= minVersion) metVersionCount++; if (metVersionCount >= requiredCount) return true; count++; } return false; }
//TODO not needed, but hanging onto //public double TargetToDifficulty(UInt256 target) //{ // // difficulty is HighestTarget / target // // since these are 256-bit numbers, use division trick for BigIntegers // return Math.Exp(BigInteger.Log(ChainParams.HighestTarget.ToBigInteger()) - BigInteger.Log(target.ToBigInteger())); //} //public UInt256 DifficultyToTarget(double difficulty) //{ // // implementation is equivalent of HighestTarget / difficulty // // multiply difficulty and HighestTarget by a scale so that the decimal portion can be fed into a BigInteger // var scale = 0x100000000L; // var highestTargetScaled = (BigInteger)ChainParams.HighestTarget * scale; // var difficultyScaled = (BigInteger)(difficulty * scale); // // do the division // var target = highestTargetScaled / difficultyScaled; // // get the resulting target bytes, taking only the 3 most significant // var targetBytes = target.ToByteArray(); // targetBytes = new byte[targetBytes.Length - 3].Concat(targetBytes.Skip(targetBytes.Length - 3).ToArray()); // // return the target // return new UInt256(targetBytes); //} public uint GetRequiredNextBits(Chain chain) { var powLimitCompact = DataCalculator.TargetToBits(ChainParams.HighestTarget); if (chain.Height == 0) return powLimitCompact; var prevHeader = chain.Blocks[chain.Height - 1]; if (ChainParams.PowNoRetargeting) return prevHeader.Bits; // not on an adjustment interval, use previous block's target if (chain.Height % ChainParams.DifficultyInterval != 0) { if (!ChainParams.AllowMininimumDifficultyBlocks) return prevHeader.Bits; else { // 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. var currentHeader = chain.LastBlock; if (currentHeader.Time > prevHeader.Time + TimeSpan.FromTicks(ChainParams.PowTargetSpacing.Ticks * 2)) return powLimitCompact; else { // Return the last non-special-min-difficulty-rules-block var header = prevHeader; while (header.Height > 0 && header.Height % ChainParams.DifficultyInterval != 0 && header.Bits == powLimitCompact) { header = chain.Blocks[header.Height - 1]; } return header.Bits; } } } // on an adjustment interval, calculate the required next target else { // get the block difficultyInterval blocks ago var prevIntervalHeight = prevHeader.Height - (ChainParams.DifficultyInterval - 1); var prevIntervalHeader = chain.Blocks[prevIntervalHeight]; var actualTimespan = (uint)(prevHeader.Time - prevIntervalHeader.Time).TotalSeconds; var targetTimespan = (uint)(ChainParams.DifficultyTargetTimespan).TotalSeconds; // limit adjustment to 4x or 1/4x if (actualTimespan < targetTimespan / 4) actualTimespan = targetTimespan / 4; else if (actualTimespan > targetTimespan * 4) actualTimespan = targetTimespan * 4; // calculate the new target var target = prevHeader.BlockHeader.CalculateTarget(); target *= (UInt256)actualTimespan; target /= (UInt256)targetTimespan; // make sure target isn't too high (too low difficulty) if (target > ChainParams.HighestTarget) target = ChainParams.HighestTarget; return DataCalculator.TargetToBits(target); } }
private async Task PruneBlockTxesAsync(PruningMode mode, Chain chain, ChainedHeader pruneBlock, BlockSpentTxes spentTxes) { if (!mode.HasFlag(PruningMode.BlockTxesPreserveMerkle) && !mode.HasFlag(PruningMode.BlockTxesDestroyMerkle)) return; // create a source of txes to prune sources, for each block var pruningQueue = new BufferBlock<Tuple<int, List<int>>>(); // prepare tx pruner, to prune a txes source for a given block var txPruner = new ActionBlock<Tuple<int, List<int>>>( blockWorkItem => { var blockIndex = blockWorkItem.Item1; var blockHash = chain.Blocks[blockIndex].Hash; var spentTxIndices = blockWorkItem.Item2; var pruneWorkItem = new KeyValuePair<UInt256, IEnumerable<int>>(blockHash, spentTxIndices); if (mode.HasFlag(PruningMode.BlockTxesPreserveMerkle)) this.storageManager.BlockTxesStorage.PruneElements(new[] { pruneWorkItem }); else this.storageManager.BlockTxesStorage.DeleteElements(new[] { pruneWorkItem }); }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }); pruningQueue.LinkTo(txPruner, new DataflowLinkOptions { PropagateCompletion = true }); // queue spent txes, grouped by block await pruningQueue.SendAndCompleteAsync( spentTxes.ReadByBlock().Select( spentTxesByBlock => { var blockIndex = spentTxesByBlock.Item1; var txIndices = spentTxesByBlock.Item2.Select(x => x.TxIndex).ToList(); return Tuple.Create(blockIndex, txIndices); })); await txPruner.Completion; }
private async Task PruneBlock(PruningMode mode, Chain chain, ChainedHeader pruneBlock) { //TODO the replay information about blocks that have been rolled back also needs to be pruned (UnmintedTx) var txCount = 0; var totalStopwatch = Stopwatch.StartNew(); var pruneBlockTxesStopwatch = new Stopwatch(); var pruneTxIndexStopwatch = new Stopwatch(); var pruneSpentTxesStopwatch = new Stopwatch(); // retrieve the spent txes for this block BlockSpentTxes spentTxes; using (var handle = this.storageManager.OpenChainStateCursor()) { var chainStateCursor = handle.Item; chainStateCursor.BeginTransaction(readOnly: true); chainStateCursor.TryGetBlockSpentTxes(pruneBlock.Height, out spentTxes); } if (spentTxes != null) { txCount = spentTxes.Count; pruneBlockTxesStopwatch.Start(); pruneTxIndexStopwatch.Start(); await Task.WhenAll( // prune block txes (either merkle prune or delete) PruneBlockTxesAsync(mode, chain, pruneBlock, spentTxes) .ContinueWith(task => { pruneBlockTxesStopwatch.Stop(); task.Wait(); }), // prune tx index PruneTxIndexAsync(mode, chain, pruneBlock, spentTxes) .ContinueWith(task => { pruneTxIndexStopwatch.Stop(); task.Wait(); }) ); // remove block spent txes information //TODO should have a buffer on removing this, block txes pruning may need it again if flush doesn't happen pruneSpentTxesStopwatch.Time(() => PruneBlockSpentTxes(mode, chain, pruneBlock)); } else //if (pruneBlock.Height > 0) { //TODO can't throw an exception unless the pruned chain is persisted //logger.Info("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: {0:N0}".Format2(pruneBlock.Height)); //throw new InvalidOperationException(); txCount = 0; } // track stats txCountMeasure.Tick(txCount); txRateMeasure.Tick((float)(txCount / totalStopwatch.Elapsed.TotalSeconds)); pruneBlockTxesDurationMeasure.Tick(pruneBlockTxesStopwatch.Elapsed); pruneTxIndexDurationMeasure.Tick(pruneTxIndexStopwatch.Elapsed); pruneSpentTxesDurationMeasure.Tick(pruneSpentTxesStopwatch.Elapsed); totalDurationMeasure.Tick(totalStopwatch.Elapsed); }
public IEnumerable <Tuple <int, ChainedHeader> > NavigateTowards(Chain targetChain) { return(this.NavigateTowards(() => targetChain)); }
public ChainState(Chain chain, Utxo utxo) { this.chain = chain; this.utxo = utxo; }
public virtual UInt256 GetRequiredNextTarget(Chain chain) { try { // genesis block, use its target if (chain.Height == 0) { // lookup genesis block header var genesisBlockHeader = chain.Blocks[0].BlockHeader; return genesisBlockHeader.CalculateTarget(); } // not on an adjustment interval, use previous block's target else if (chain.Height % DifficultyInterval != 0) { // lookup the previous block on the current blockchain var prevBlockHeader = chain.Blocks[chain.Height - 1].BlockHeader; return prevBlockHeader.CalculateTarget(); } // on an adjustment interval, calculate the required next target else { // lookup the previous block on the current blockchain var prevBlockHeader = chain.Blocks[chain.Height - 1].BlockHeader; // get the block difficultyInterval blocks ago var startBlockHeader = chain.Blocks[chain.Height - DifficultyInterval].BlockHeader; //Debug.Assert(startChainedHeader.Height == blockchain.Height - DifficultyInternal); var actualTimespan = (long)prevBlockHeader.Time - (long)startBlockHeader.Time; var targetTimespan = DifficultyTargetTimespan; // limit adjustment to 4x or 1/4x if (actualTimespan < targetTimespan / 4) actualTimespan = targetTimespan / 4; else if (actualTimespan > targetTimespan * 4) actualTimespan = targetTimespan * 4; // calculate the new target var target = startBlockHeader.CalculateTarget(); target *= (UInt256)actualTimespan; target /= (UInt256)targetTimespan; // make sure target isn't too high (too low difficulty) if (target > HighestTarget) target = HighestTarget; return target; } } catch (ArgumentException) { // invalid bits throw new ValidationException(chain.LastBlock.Hash); } }
public IEnumerable<Tuple<int, ChainedHeader>> NavigateTowards(Chain chain) { return NavigateTowards(() => chain); }
public void PostValidateBlock(Chain newChain, object finalTally) { var chainedHeader = newChain.LastBlock; var blockTally = (BlockTally)finalTally; // ensure there is at least 1 transaction if (blockTally.TxCount == 0) throw new ValidationException(chainedHeader.Hash, $"Failing block {chainedHeader.Hash} at height {chainedHeader.Height}: Zero transactions present"); // ensure fees aren't negative (should be caught earlier) if (blockTally.TotalFees < 0) throw new ValidationException(chainedHeader.Hash); // calculate the expected reward in coinbase var subsidy = (ulong)(50 * SATOSHI_PER_BTC); if (chainedHeader.Height / 210000 <= 32) subsidy /= (ulong)Math.Pow(2, chainedHeader.Height / 210000); // calculate the expected reward in coinbase var expectedReward = subsidy + (ulong)blockTally.TotalFees; // calculate the actual reward in coinbase var actualReward = blockTally.CoinbaseTx.Transaction.Outputs.Sum(x => x.Value); // ensure coinbase has correct reward if (actualReward > expectedReward) { throw new ValidationException(chainedHeader.Hash, $"Failing block {chainedHeader.Hash} at height {chainedHeader.Height}: Coinbase value is greater than reward + fees"); } }
private async Task PruneTxIndexAsync(PruningMode mode, Chain chain, ChainedHeader pruneBlock, BlockSpentTxes spentTxes) { if (!mode.HasFlag(PruningMode.TxIndex)) return; var maxParallelism = Environment.ProcessorCount; // prepare a cache of cursors to be used by the pruning action block, allowing a pool of transactions var openedCursors = new ConcurrentBag<IChainStateCursor>(); using (var cursorHandles = new DisposableCache<DisposeHandle<IChainStateCursor>>(maxParallelism, () => { // retrieve a new cursor and start its transaction, keeping track of any cursors opened var cursorHandle = this.storageManager.OpenChainStateCursor(); cursorHandle.Item.BeginTransaction(pruneOnly: true); openedCursors.Add(cursorHandle.Item); return cursorHandle; })) { var pruneTxIndex = new ActionBlock<SpentTx>( spentTx => { using (var handle = cursorHandles.TakeItem()) { var chainStateCursor = handle.Item.Item; chainStateCursor.TryRemoveUnspentTx(spentTx.TxHash); } }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = maxParallelism }); var spentTxesQueue = new BufferBlock<SpentTx>(); spentTxesQueue.LinkTo(pruneTxIndex, new DataflowLinkOptions { PropagateCompletion = true }); await spentTxesQueue.SendAndCompleteAsync(spentTxes); await pruneTxIndex.Completion; // commit all opened cursors on success Parallel.ForEach(openedCursors, cursor => cursor.CommitTransaction()); } }
public void ValidateTransaction(Chain newChain, ValidatableTx validatableTx) { // TODO any expensive validation can go here }
private void PruneBlockSpentTxes(PruningMode mode, Chain chain, ChainedHeader pruneBlock) { if (!mode.HasFlag(PruningMode.BlockSpentIndex)) return; using (var handle = this.storageManager.OpenChainStateCursor()) { var chainStateCursor = handle.Item; chainStateCursor.BeginTransaction(pruneOnly: true); // TODO don't immediately remove list of spent txes per block from chain state, // use an additional safety buffer in case there was an issue pruning block // txes (e.g. didn't flush and crashed), keeping the information will allow // the block txes pruning to be performed again chainStateCursor.TryRemoveBlockSpentTxes(pruneBlock.Height); chainStateCursor.CommitTransaction(); } }
/// <summary> /// Enumerate the path of headers to get from this chain to the target chain. /// </summary> /// <param name="targetChain">The chain to navigate towards.</param> /// <returns>An enumeration of the path's headers.</returns> /// <exception cref="InvalidOperationException">Thrown if there is no path to the target chain.</exception> public IEnumerable<Tuple<int, ChainedHeader>> NavigateTowards(Chain targetChain) { return this.NavigateTowards(() => targetChain); }
/// <summary> /// Create a new builder from parentChain. Changes to the builder have no effect on parentChain. /// </summary> /// <param name="parentChain">The parent chain to copy.</param> public ChainBuilder(Chain parentChain) { this.blocks = parentChain.Blocks.ToBuilder(); this.blocksByHash = parentChain.BlocksByHash.ToBuilder(); }
public ISourceBlock<ValidatableTx> CalculateUtxo(IChainStateCursor chainStateCursor, Chain chain, ISourceBlock<DecodedBlockTx> blockTxes, CancellationToken cancelToken = default(CancellationToken)) { var chainedHeader = chain.LastBlock; var blockSpentTxes = new BlockSpentTxesBuilder(); var utxoCalculator = new TransformBlock<DecodedBlockTx, ValidatableTx>( blockTx => { var tx = blockTx.Transaction; var txIndex = blockTx.Index; var prevTxOutputs = ImmutableArray.CreateBuilder<PrevTxOutput>(!blockTx.IsCoinbase ? tx.Inputs.Length : 0); //TODO apply real coinbase rule // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219 if (!blockTx.IsCoinbase) { // spend each of the transaction's inputs in the utxo for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; var prevTxOutput = this.Spend(chainStateCursor, txIndex, tx, inputIndex, input, chainedHeader, blockSpentTxes); prevTxOutputs.Add(prevTxOutput); } } // there exist two duplicate coinbases in the blockchain, which the design assumes to be impossible // ignore the first occurrences of these duplicates so that they do not need to later be deleted from the utxo, an unsupported operation // no other duplicates will occur again, it is now disallowed var isDupeCoinbase = IsDupeCoinbase(chainedHeader, tx); // add transaction's outputs to utxo, except for the genesis block and the duplicate coinbases if (chainedHeader.Height > 0 && !isDupeCoinbase) { // mint the transaction's outputs in the utxo this.Mint(chainStateCursor, tx, txIndex, chainedHeader); // increase unspent output count chainStateCursor.UnspentOutputCount += tx.Outputs.Length; // increment unspent tx count chainStateCursor.UnspentTxCount++; chainStateCursor.TotalTxCount++; chainStateCursor.TotalInputCount += tx.Inputs.Length; chainStateCursor.TotalOutputCount += tx.Outputs.Length; } return new ValidatableTx(blockTx, chainedHeader, prevTxOutputs.MoveToImmutable()); }, new ExecutionDataflowBlockOptions { CancellationToken = cancelToken }); blockTxes.LinkTo(utxoCalculator, new DataflowLinkOptions { PropagateCompletion = true }); return OnCompleteBlock.Create(utxoCalculator, () => { if (!chainStateCursor.TryAddBlockSpentTxes(chainedHeader.Height, blockSpentTxes.ToImmutable())) throw new ValidationException(chainedHeader.Hash); }, cancelToken); }