/// <summary> /// Context-dependent validity checks. /// </summary> /// <param name="context">Context that contains variety of information regarding blocks validation and execution.</param> /// <exception cref="ConsensusErrors.BadTransactionNonFinal">Thrown if one or more transactions are not finalized.</exception> /// <exception cref="ConsensusErrors.BadCoinbaseHeight">Thrown if coinbase doesn't start with serialized block height.</exception> public virtual void ContextualCheckBlock(RuleContext context) { this.logger.LogTrace("()"); Block block = context.BlockValidationContext.Block; DeploymentFlags deploymentFlags = context.Flags; int height = context.BestBlock == null ? 0 : context.BestBlock.Height + 1; // Start enforcing BIP113 (Median Time Past) using versionbits logic. DateTimeOffset lockTimeCutoff = deploymentFlags.LockTimeFlags.HasFlag(Transaction.LockTimeFlags.MedianTimePast) ? context.BestBlock.MedianTimePast : block.Header.BlockTime; // Check that all transactions are finalized. foreach (Transaction transaction in block.Transactions) { if (!transaction.IsFinal(lockTimeCutoff, height)) { this.logger.LogTrace("(-)[TX_NON_FINAL]"); ConsensusErrors.BadTransactionNonFinal.Throw(); } } // Enforce rule that the coinbase starts with serialized block height. if (deploymentFlags.EnforceBIP34) { var expect = new Script(Op.GetPushOp(height)); Script actual = block.Transactions[0].Inputs[0].ScriptSig; if (!this.StartWith(actual.ToBytes(true), expect.ToBytes(true))) { this.logger.LogTrace("(-)[BAD_COINBASE_HEIGHT]"); ConsensusErrors.BadCoinbaseHeight.Throw(); } } // Validation for witness commitments. // * We compute the witness hash (which is the hash including witnesses) of all the block's transactions, except the // coinbase (where 0x0000....0000 is used instead). // * The coinbase scriptWitness is a stack of a single 32-byte vector, containing a witness nonce (unconstrained). // * We build a merkle tree with all those witness hashes as leaves (similar to the hashMerkleRoot in the block header). // * There must be at least one output whose scriptPubKey is a single 36-byte push, the first 4 bytes of which are // {0xaa, 0x21, 0xa9, 0xed}, and the following 32 bytes are SHA256^2(witness root, witness nonce). In case there are // multiple, the last one is used. bool haveWitness = false; if (deploymentFlags.ScriptFlags.HasFlag(ScriptVerify.Witness)) { int commitpos = this.GetWitnessCommitmentIndex(block); if (commitpos != -1) { uint256 hashWitness = this.BlockWitnessMerkleRoot(block, out bool unused); // The malleation check is ignored; as the transaction tree itself // already does not permit it, it is impossible to trigger in the // witness tree. WitScript witness = block.Transactions[0].Inputs[0].WitScript; if ((witness.PushCount != 1) || (witness.Pushes.First().Length != 32)) { this.logger.LogTrace("(-)[BAD_WITNESS_NONCE_SIZE]"); ConsensusErrors.BadWitnessNonceSize.Throw(); } var hashed = new byte[64]; Buffer.BlockCopy(hashWitness.ToBytes(), 0, hashed, 0, 32); Buffer.BlockCopy(witness.Pushes.First(), 0, hashed, 32, 32); hashWitness = Hashes.Hash256(hashed); if (!this.EqualsArray(hashWitness.ToBytes(), block.Transactions[0].Outputs[commitpos].ScriptPubKey.ToBytes(true).Skip(6).ToArray(), 32)) { this.logger.LogTrace("(-)[WITNESS_MERKLE_MISMATCH]"); ConsensusErrors.BadWitnessMerkleMatch.Throw(); } haveWitness = true; } } if (!haveWitness) { for (int i = 0; i < block.Transactions.Count; i++) { if (block.Transactions[i].HasWitness) { this.logger.LogTrace("(-)[UNEXPECTED_WITNESS]"); ConsensusErrors.UnexpectedWitness.Throw(); } } } // After the coinbase witness nonce and commitment are verified, // we can check if the block weight passes (before we've checked the // coinbase witness, it would be possible for the weight to be too // large by filling up the coinbase witness, which doesn't change // the block hash, so we couldn't mark the block as permanently // failed). if (this.GetBlockWeight(block) > this.ConsensusOptions.MaxBlockWeight) { this.logger.LogTrace("(-)[BAD_BLOCK_WEIGHT]"); ConsensusErrors.BadBlockWeight.Throw(); } this.logger.LogTrace("(-)[OK]"); }
/// <summary> /// Checks block's validity. /// </summary> /// <param name="context">Context that contains variety of information regarding blocks validation and execution.</param> /// <exception cref="ConsensusErrors.BadMerkleRoot">Thrown block's merkle root is corrupted.</exception> /// <exception cref="ConsensusErrors.BadTransactionDuplicate">Thrown if block contains duplicated transaction that don't affect merkle root.</exception> /// <exception cref="ConsensusErrors.BadBlockLength">Thrown if block exceeds maximum allowed size or doesn't contain any transaction.</exception> /// <exception cref="ConsensusErrors.BadCoinbaseMissing">Thrown if block's first transaction is not coinbase.</exception> /// <exception cref="ConsensusErrors.BadMultipleCoinbase">Thrown if block contains more then one coinbase transactions.</exception> /// <exception cref="ConsensusErrors.BadBlockSigOps">Thrown if block's signature operation cost is greater than maximum allowed one.</exception> public virtual void CheckBlock(RuleContext context) { this.logger.LogTrace("()"); Block block = context.BlockValidationContext.Block; bool mutated; uint256 hashMerkleRoot2 = this.BlockMerkleRoot(block, out mutated); if (context.CheckMerkleRoot && (block.Header.HashMerkleRoot != hashMerkleRoot2)) { this.logger.LogTrace("(-)[BAD_MERKLE_ROOT]"); ConsensusErrors.BadMerkleRoot.Throw(); } // Check for merkle tree malleability (CVE-2012-2459): repeating sequences // of transactions in a block without affecting the merkle root of a block, // while still invalidating it. if (mutated) { this.logger.LogTrace("(-)[BAD_TX_DUP]"); ConsensusErrors.BadTransactionDuplicate.Throw(); } // All potential-corruption validation must be done before we do any // transaction validation, as otherwise we may mark the header as invalid // because we receive the wrong transactions for it. // Note that witness malleability is checked in ContextualCheckBlock, so no // checks that use witness data may be performed here. // Size limits. if ((block.Transactions.Count == 0) || (block.Transactions.Count > this.ConsensusOptions.MaxBlockBaseSize) || (this.GetSize(block, NetworkOptions.None) > this.ConsensusOptions.MaxBlockBaseSize)) { this.logger.LogTrace("(-)[BAD_BLOCK_LEN]"); ConsensusErrors.BadBlockLength.Throw(); } // First transaction must be coinbase, the rest must not be if ((block.Transactions.Count == 0) || !block.Transactions[0].IsCoinBase) { this.logger.LogTrace("(-)[NO_COINBASE]"); ConsensusErrors.BadCoinbaseMissing.Throw(); } for (int i = 1; i < block.Transactions.Count; i++) { if (block.Transactions[i].IsCoinBase) { this.logger.LogTrace("(-)[MULTIPLE_COINBASE]"); ConsensusErrors.BadMultipleCoinbase.Throw(); } } // Check transactions foreach (Transaction tx in block.Transactions) { this.CheckTransaction(tx); } long sigOps = 0; foreach (Transaction tx in block.Transactions) { sigOps += this.GetLegacySignatureOperationsCount(tx); } if ((sigOps * this.ConsensusOptions.WitnessScaleFactor) > this.ConsensusOptions.MaxBlockSigopsCost) { this.logger.LogTrace("(-)[BAD_BLOCK_SIGOPS]"); ConsensusErrors.BadBlockSigOps.Throw(); } this.logger.LogTrace("(-)[OK]"); }
/// <summary> /// Checks and computes stake. /// </summary> /// <param name="context">Context that contains variety of information regarding blocks validation and execution.</param> /// <exception cref="ConsensusErrors.PrevStakeNull">Thrown if previous stake is not found.</exception> /// <exception cref="ConsensusErrors.SetStakeEntropyBitFailed">Thrown if failed to set stake entropy bit.</exception> private void CheckAndComputeStake(RuleContext context) { this.logger.LogTrace("()"); ChainedBlock chainedBlock = context.BlockValidationContext.ChainedBlock; Block block = context.BlockValidationContext.Block; BlockStake blockStake = context.Stake.BlockStake; // Verify hash target and signature of coinstake tx. if (BlockStake.IsProofOfStake(block)) { ChainedBlock prevChainedBlock = chainedBlock.Previous; BlockStake prevBlockStake = this.stakeChain.Get(prevChainedBlock.HashBlock); if (prevBlockStake == null) { ConsensusErrors.PrevStakeNull.Throw(); } // Only do proof of stake validation for blocks that are after the assumevalid block or after the last checkpoint. if (!context.SkipValidation) { this.StakeValidator.CheckProofOfStake(context.Stake, prevChainedBlock, prevBlockStake, block.Transactions[1], chainedBlock.Header.Bits.ToCompact()); } else { this.logger.LogTrace("POS validation skipped for block at height {0}.", chainedBlock.Height); } } // PoW is checked in CheckBlock(). if (BlockStake.IsProofOfWork(block)) { context.Stake.HashProofOfStake = chainedBlock.Header.GetPoWHash(); } // Compute stake entropy bit for stake modifier. if (!blockStake.SetStakeEntropyBit(blockStake.GetStakeEntropyBit())) { this.logger.LogTrace("(-)[STAKE_ENTROPY_BIT_FAIL]"); ConsensusErrors.SetStakeEntropyBitFailed.Throw(); } // Record proof hash value. blockStake.HashProof = context.Stake.HashProofOfStake; int lastCheckpointHeight = this.Checkpoints.GetLastCheckpointHeight(); if (chainedBlock.Height > lastCheckpointHeight) { // Compute stake modifier. ChainedBlock prevChainedBlock = chainedBlock.Previous; BlockStake blockStakePrev = prevChainedBlock == null ? null : this.stakeChain.Get(prevChainedBlock.HashBlock); blockStake.StakeModifierV2 = this.StakeValidator.ComputeStakeModifierV2(prevChainedBlock, blockStakePrev, blockStake.IsProofOfWork() ? chainedBlock.HashBlock : blockStake.PrevoutStake.Hash); } else if (chainedBlock.Height == lastCheckpointHeight) { // Copy checkpointed stake modifier. CheckpointInfo checkpoint = this.Checkpoints.GetCheckpoint(lastCheckpointHeight); blockStake.StakeModifierV2 = checkpoint.StakeModifierV2; this.logger.LogTrace("Last checkpoint stake modifier V2 loaded: '{0}'.", blockStake.StakeModifierV2); } else { this.logger.LogTrace("POS stake modifier computation skipped for block at height {0} because it is not above last checkpoint block height {1}.", chainedBlock.Height, lastCheckpointHeight); } this.logger.LogTrace("(-)[OK]"); }
/// <summary> /// Validates the UTXO set is correctly spent. /// </summary> /// <param name="context">Context that contains variety of information regarding blocks validation and execution.</param> /// <param name="taskScheduler">Task scheduler for creating tasks that would check validity of each transaction input.</param> /// <exception cref="ConsensusErrors.BadTransactionBIP30">Thrown if block contain transactions which 'overwrite' older transactions.</exception> /// <exception cref="ConsensusErrors.BadTransactionMissingInput">Thrown if transaction tries to spend inputs that are missing.</exception> /// <exception cref="ConsensusErrors.BadTransactionNonFinal">Thrown if transaction's height or time is lower then provided by SequenceLock for this block.</exception> /// <exception cref="ConsensusErrors.BadBlockSigOps">Thrown if signature operation cost is greater then maximum block signature operation cost.</exception> /// <exception cref="ConsensusErrors.BadTransactionScriptError">Thrown if not all inputs are valid (no double spends, scripts & sigs, amounts).</exception> public virtual void ExecuteBlock(RuleContext context, TaskScheduler taskScheduler = null) { this.logger.LogTrace("()"); Block block = context.BlockValidationContext.Block; ChainedBlock index = context.BlockValidationContext.ChainedBlock; DeploymentFlags flags = context.Flags; UnspentOutputSet view = context.Set; this.PerformanceCounter.AddProcessedBlocks(1); taskScheduler = taskScheduler ?? TaskScheduler.Default; if (!context.SkipValidation) { if (flags.EnforceBIP30) { foreach (Transaction tx in block.Transactions) { UnspentOutputs coins = view.AccessCoins(tx.GetHash()); if ((coins != null) && !coins.IsPrunable) { this.logger.LogTrace("(-)[BAD_TX_BIP_30]"); ConsensusErrors.BadTransactionBIP30.Throw(); } } } } else { this.logger.LogTrace("BIP30 validation skipped for checkpointed block at height {0}.", index.Height); } long sigOpsCost = 0; Money fees = Money.Zero; var checkInputs = new List <Task <bool> >(); for (int txIndex = 0; txIndex < block.Transactions.Count; txIndex++) { this.PerformanceCounter.AddProcessedTransactions(1); Transaction tx = block.Transactions[txIndex]; if (!context.SkipValidation) { if (!tx.IsCoinBase && (!context.IsPoS || (context.IsPoS && !tx.IsCoinStake))) { int[] prevheights; if (!view.HaveInputs(tx)) { this.logger.LogTrace("(-)[BAD_TX_NO_INPUT]"); ConsensusErrors.BadTransactionMissingInput.Throw(); } prevheights = new int[tx.Inputs.Count]; // Check that transaction is BIP68 final. // BIP68 lock checks (as opposed to nLockTime checks) must // be in ConnectBlock because they require the UTXO set. for (int j = 0; j < tx.Inputs.Count; j++) { prevheights[j] = (int)view.AccessCoins(tx.Inputs[j].PrevOut.Hash).Height; } if (!tx.CheckSequenceLocks(prevheights, index, flags.LockTimeFlags)) { this.logger.LogTrace("(-)[BAD_TX_NON_FINAL]"); ConsensusErrors.BadTransactionNonFinal.Throw(); } } // GetTransactionSignatureOperationCost counts 3 types of sigops: // * legacy (always), // * p2sh (when P2SH enabled in flags and excludes coinbase), // * witness (when witness enabled in flags and excludes coinbase). sigOpsCost += this.GetTransactionSignatureOperationCost(tx, view, flags); if (sigOpsCost > this.ConsensusOptions.MaxBlockSigopsCost) { ConsensusErrors.BadBlockSigOps.Throw(); } // TODO: Simplify this condition. if (!tx.IsCoinBase && (!context.IsPoS || (context.IsPoS && !tx.IsCoinStake))) { this.CheckInputs(tx, view, index.Height); fees += view.GetValueIn(tx) - tx.TotalOut; var txData = new PrecomputedTransactionData(tx); for (int inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++) { this.PerformanceCounter.AddProcessedInputs(1); TxIn input = tx.Inputs[inputIndex]; int inputIndexCopy = inputIndex; TxOut txout = view.GetOutputFor(input); var checkInput = new Task <bool>(() => { var checker = new TransactionChecker(tx, inputIndexCopy, txout.Value, txData); var ctx = new ScriptEvaluationContext(); ctx.ScriptVerify = flags.ScriptFlags; return(ctx.VerifyScript(input.ScriptSig, txout.ScriptPubKey, checker)); }); checkInput.Start(taskScheduler); checkInputs.Add(checkInput); } } } this.UpdateCoinView(context, tx); } if (!context.SkipValidation) { this.CheckBlockReward(context, fees, index.Height, block); bool passed = checkInputs.All(c => c.GetAwaiter().GetResult()); if (!passed) { this.logger.LogTrace("(-)[BAD_TX_SCRIPT]"); ConsensusErrors.BadTransactionScriptError.Throw(); } } else { this.logger.LogTrace("BIP68, SigOp cost, and block reward validation skipped for block at height {0}.", index.Height); } this.logger.LogTrace("(-)"); }
/// <inheritdoc /> public override void CheckBlock(RuleContext context) { this.logger.LogTrace("()"); base.CheckBlock(context); Block block = context.BlockValidationContext.Block; // Check timestamp. if (block.Header.Time > this.FutureDrift(this.dateTimeProvider.GetAdjustedTimeAsUnixTimestamp())) { // The block can be valid only after its time minus the future drift. context.BlockValidationContext.RejectUntil = Utils.UnixTimeToDateTime(block.Header.Time - this.FutureDrift(0)).UtcDateTime; this.logger.LogTrace("(-)[TIME_TOO_FAR]"); ConsensusErrors.BlockTimestampTooFar.Throw(); } if (BlockStake.IsProofOfStake(block)) { // Coinbase output should be empty if proof-of-stake block. if ((block.Transactions[0].Outputs.Count != 1) || !block.Transactions[0].Outputs[0].IsEmpty) { this.logger.LogTrace("(-)[COINBASE_NOT_EMPTY]"); ConsensusErrors.BadStakeBlock.Throw(); } // Second transaction must be coinstake, the rest must not be. if (!block.Transactions[1].IsCoinStake) { this.logger.LogTrace("(-)[NO_COINSTAKE]"); ConsensusErrors.BadStakeBlock.Throw(); } if (block.Transactions.Skip(2).Any(t => t.IsCoinStake)) { this.logger.LogTrace("(-)[MULTIPLE_COINSTAKE]"); ConsensusErrors.BadMultipleCoinstake.Throw(); } } // Check proof-of-stake block signature. if (!this.CheckBlockSignature(block)) { this.logger.LogTrace("(-)[BAD_SIGNATURE]"); ConsensusErrors.BadBlockSignature.Throw(); } // Check transactions. foreach (Transaction transaction in block.Transactions) { // Check transaction timestamp. if (block.Header.Time < transaction.Time) { this.logger.LogTrace("Block contains transaction with timestamp {0}, which is greater than block's timestamp {1}.", transaction.Time, block.Header.Time); this.logger.LogTrace("(-)[TX_TIME_MISMATCH]"); ConsensusErrors.BlockTimeBeforeTrx.Throw(); } } this.logger.LogTrace("(-)[OK]"); }