Example #1
0
 public void ValidateTransaction(Chain newChain, ValidatableTx validatableTx)
 {
     if (ValidateTransactionAction == null)
         coreRules.ValidateTransaction(newChain, validatableTx);
     else
         ValidateTransactionAction(newChain, validatableTx);
 }
Example #2
0
 public void PostValidateBlock(Chain newChain, object finalTally)
 {
     if (PreValidateBlockAction == null)
         coreRules.PostValidateBlock(newChain, finalTally);
     else
         PostValidateBlockAction(newChain, finalTally);
 }
Example #3
0
 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);
 }
Example #4
0
 public void PreValidateBlock(Chain newChain)
 {
     if (PreValidateBlockAction == null)
         coreRules.PreValidateBlock(newChain);
     else
         PreValidateBlockAction(newChain);
 }
Example #5
0
        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);
        }
Example #6
0
        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
        }
Example #7
0
 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);
     }
 }
Example #8
0
        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 });
        }
Example #9
0
        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 });
        }
Example #10
0
        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();
            }
        }
Example #11
0
        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);
            }
        }
Example #12
0
        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();
                }
            }
        }
Example #14
0
 public bool TryReadChain(UInt256 blockHash, out Chain chain)
 {
     return Chain.TryReadChain(blockHash, out chain,
         headerHash =>
         {
             ChainedHeader chainedHeader;
             TryGetChainedHeader(headerHash, out chainedHeader);
             return chainedHeader;
         });
 }
Example #15
0
        //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);
            }
        }
Example #16
0
        //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);
            }
        }
Example #17
0
        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
        }
Example #18
0
        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;
        }
Example #19
0
        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());
            }
        }
Example #20
0
        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;
        }
Example #21
0
        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);
        }
Example #22
0
        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);
            }
        }
Example #23
0
 public IEnumerable<Tuple<int, ChainedHeader>> NavigateTowards(Chain chain)
 {
     return NavigateTowards(() => chain);
 }
Example #24
0
        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");
            }
        }
Example #25
0
        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();
            }
        }
Example #26
0
 public void ValidateTransaction(Chain newChain, ValidatableTx validatableTx)
 {
     // TODO any expensive validation can go here
 }
Example #27
0
 /// <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);
 }
Example #28
0
        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;
            }
        }
Example #29
0
 /// <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();
 }
Example #30
0
        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);
        }