private long CountWitnessSigOps(Script scriptSig, Script scriptPubKey, WitScript witness, DeploymentFlags flags) { witness = witness ?? WitScript.Empty; if (!flags.ScriptFlags.HasFlag(ScriptVerify.Witness)) { return(0); } WitProgramParameters witParams = PayToWitTemplate.Instance.ExtractScriptPubKeyParameters2(scriptPubKey); if (witParams != null) { return(this.WitnessSigOps(witParams, witness, flags)); } if (scriptPubKey.IsPayToScriptHash && scriptSig.IsPushOnly) { byte[] data = scriptSig.ToOps().Select(o => o.PushData).LastOrDefault() ?? new byte[0]; Script subScript = Script.FromBytesUnsafe(data); witParams = PayToWitTemplate.Instance.ExtractScriptPubKeyParameters2(scriptPubKey); if (witParams != null) { return(this.WitnessSigOps(witParams, witness, flags)); } } return(0); }
private long WitnessSigOps(WitProgramParameters witParams, WitScript witScript, DeploymentFlags flags) { if (witParams.Version == 0) { if (witParams.Program.Length == 20) { return(1); } if (witParams.Program.Length == 32 && witScript.PushCount > 0) { Script subscript = Script.FromBytesUnsafe(witScript.GetUnsafePush(witScript.PushCount - 1)); return(subscript.GetSigOpCount(true)); } } // Future flags may be implemented here. return(0); }
/// <inheritdoc /> public override async Task RunAsync(RuleContext context) { Block block = context.ValidationContext.BlockToValidate; ChainedHeader index = context.ValidationContext.ChainedHeaderToValidate; DeploymentFlags flags = context.Flags; UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet; long sigOpsCost = 0; Money fees = Money.Zero; var inputsToCheck = new List <(Transaction tx, int inputIndexCopy, TxOut txOut, PrecomputedTransactionData txData, TxIn input, DeploymentFlags flags)>(); for (int txIndex = 0; txIndex < block.Transactions.Count; txIndex++) { Transaction tx = block.Transactions[txIndex]; if (!context.SkipValidation) { if (!tx.IsCoinBase && !view.HaveInputs(tx)) { this.Logger.LogDebug("Transaction '{0}' has no inputs", tx.GetHash()); this.Logger.LogTrace("(-)[BAD_TX_NO_INPUT]"); ConsensusErrors.BadTransactionMissingInput.Throw(); } if (!this.IsTxFinal(tx, context)) { this.Logger.LogDebug("Transaction '{0}' is not final", tx.GetHash()); 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) { this.Logger.LogTrace("(-)[BAD_BLOCK_SIG_OPS]"); ConsensusErrors.BadBlockSigOps.Throw(); } if (!tx.IsCoinBase) { this.CheckInputs(tx, view, index.Height); fees += this.GetTransactionFee(view, tx); var txData = new PrecomputedTransactionData(tx); for (int inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++) { TxIn input = tx.Inputs[inputIndex]; TxOut prevOut = view.GetOutputFor(input); // If there are any consensus-specific requirements to inhibit spends from or to particular scripts, they get enforced here. this.AllowSpend(prevOut, tx); inputsToCheck.Add(( tx: tx, inputIndexCopy: inputIndex, txOut: prevOut, txData, input: input, flags )); } } } this.UpdateCoinView(context, tx); } if (!context.SkipValidation) { this.CheckBlockReward(context, fees, index.Height, block); // Start the Parallel loop on a thread so its result can be awaited rather than blocking Task <ParallelLoopResult> checkInputsInParallel = Task.Run(() => { return(Parallel.ForEach(inputsToCheck, (input, state) => { if (state.ShouldExitCurrentIteration) { return; } if (!this.CheckInput(input.tx, input.inputIndexCopy, input.txOut, input.txData, input.input, input.flags)) { state.Stop(); } })); }); ParallelLoopResult loopResult = await checkInputsInParallel.ConfigureAwait(false); if (!loopResult.IsCompleted) { this.Logger.LogTrace("(-)[BAD_TX_SCRIPT]"); ConsensusErrors.BadTransactionScriptError.Throw(); } } else { this.Logger.LogDebug("BIP68, SigOp cost, and block reward validation skipped for block at height {0}.", index.Height); } }
public virtual void ExecuteBlock(ContextInformation context, TaskScheduler taskScheduler) { this.logger.LogTrace("()"); Block block = context.BlockValidationContext.Block; ChainedBlock index = context.BlockValidationContext.ChainedBlock; DeploymentFlags flags = context.Flags; UnspentOutputSet view = context.Set; int lastCheckpointHeight = this.Checkpoints.GetLastCheckpointHeight(); bool doFullValidation = index.Height > lastCheckpointHeight; this.PerformanceCounter.AddProcessedBlocks(1); taskScheduler = taskScheduler ?? TaskScheduler.Default; if (doFullValidation) { 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 nSigOpsCost = 0; Money nFees = Money.Zero; List <Task <bool> > checkInputs = new List <Task <bool> >(); for (int txIndex = 0; txIndex < block.Transactions.Count; txIndex++) { this.PerformanceCounter.AddProcessedTransactions(1); Transaction tx = block.Transactions[txIndex]; if (doFullValidation) { 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(); } } // GetTransactionSigOpCost 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). nSigOpsCost += this.GetTransactionSigOpCost(tx, view, flags); if (nSigOpsCost > 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); nFees += view.GetValueIn(tx) - tx.TotalOut; Transaction localTx = tx; PrecomputedTransactionData 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>(() => { if (this.UseConsensusLib) { Script.BitcoinConsensusError error; return(Script.VerifyScriptConsensus(txout.ScriptPubKey, tx, (uint)inputIndexCopy, flags.ScriptFlags, out error)); } else { 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 (doFullValidation) { this.CheckBlockReward(context, nFees, index, 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 checkpointed block at height {0}.", index.Height); } this.logger.LogTrace("(-)"); }
/// <summary> /// Calculates total signature operation cost of a transaction. /// </summary> /// <param name="transaction">Transaction for which we are computing the cost.</param> /// <param name="inputs">Map of previous transactions that have outputs we're spending.</param> /// <param name="flags">Script verification flags.</param> /// <returns>Signature operation cost for all transaction's inputs.</returns> public long GetTransactionSignatureOperationCost(Transaction transaction, UnspentOutputSet inputs, DeploymentFlags flags) { long signatureOperationCost = this.GetLegacySignatureOperationsCount(transaction) * this.ConsensusOptions.WitnessScaleFactor; if (transaction.IsCoinBase) { return(signatureOperationCost); } if (flags.ScriptFlags.HasFlag(ScriptVerify.P2SH)) { signatureOperationCost += this.GetP2SHSignatureOperationsCount(transaction, inputs) * this.ConsensusOptions.WitnessScaleFactor; } for (int i = 0; i < transaction.Inputs.Count; i++) { TxOut prevout = inputs.GetOutputFor(transaction.Inputs[i]); signatureOperationCost += this.CountWitnessSignatureOperation(prevout.ScriptPubKey, transaction.Inputs[i].WitScript, flags); } return(signatureOperationCost); }
/// <summary> /// Calculates signature operation cost for single transaction input. /// </summary> /// <param name="scriptPubKey">Script public key.</param> /// <param name="witness">Witness script.</param> /// <param name="flags">Script verification flags.</param> /// <returns>Signature operation cost for single transaction input.</returns> private long CountWitnessSignatureOperation(Script scriptPubKey, WitScript witness, DeploymentFlags flags) { witness = witness ?? WitScript.Empty; if (!flags.ScriptFlags.HasFlag(ScriptVerify.Witness)) { return(0); } WitProgramParameters witParams = PayToWitTemplate.Instance.ExtractScriptPubKeyParameters2(this.Parent.Network, scriptPubKey); if (witParams?.Version == 0) { if (witParams.Program.Length == 20) { return(1); } if (witParams.Program.Length == 32 && witness.PushCount > 0) { Script subscript = Script.FromBytesUnsafe(witness.GetUnsafePush(witness.PushCount - 1)); return(subscript.GetSigOpCount(true, this.Parent.Network)); } } return(0); }
protected virtual bool IsWitnessEnabled(ChainedHeader chainedHeader) { DeploymentFlags flags = this.nodeDeployments.GetFlags(chainedHeader); return(flags.ScriptFlags.HasFlag(ScriptVerify.Witness)); }
/// <summary> /// Verify that an input may be validly spent as part of the given transaction in the given block. /// </summary> /// <param name="tx">Transaction to check.</param> /// <param name="inputIndexCopy">Index of the input to check.</param> /// <param name="txout">Output the input is spending.</param> /// <param name="txData">Transaction data for the transaction being checked.</param> /// <param name="input">Input to check.</param> /// <param name="flags">Deployment flags</param> /// <returns>Whether the input is valid.</returns> protected virtual bool CheckInput(Transaction tx, int inputIndexCopy, TxOut txout, PrecomputedTransactionData txData, TxIn input, DeploymentFlags flags) { var checker = new TransactionChecker(tx, inputIndexCopy, txout.Value, txData); var ctx = new ScriptEvaluationContext(this.Parent.Network); ctx.ScriptVerify = flags.ScriptFlags; bool verifyScriptResult = ctx.VerifyScript(input.ScriptSig, txout.ScriptPubKey, checker); if (verifyScriptResult == false) { this.Logger.LogDebug("Verify script for transaction '{0}' failed, ScriptSig = '{1}', ScriptPubKey = '{2}', script evaluation error = '{3}'", tx.GetHash(), input.ScriptSig, txout.ScriptPubKey, ctx.Error); } return(verifyScriptResult); }
public bool CheckInput(Func <Transaction, int, TxOut, PrecomputedTransactionData, TxIn, DeploymentFlags, bool> baseCheckInput, Transaction tx, int inputIndexCopy, TxOut txout, PrecomputedTransactionData txData, TxIn input, DeploymentFlags flags) { if (txout.ScriptPubKey.IsSmartContractExec() || txout.ScriptPubKey.IsSmartContractInternalCall()) { return(input.ScriptSig.IsSmartContractSpend()); } return(baseCheckInput(tx, inputIndexCopy, txout, txData, input, flags)); }
/// <inheritdoc/> protected override bool CheckInput(Transaction tx, int inputIndexCopy, TxOut txout, PrecomputedTransactionData txData, TxIn input, DeploymentFlags flags) { return(this.logic.CheckInput(base.CheckInput, tx, inputIndexCopy, txout, txData, input, flags)); }
/// <inheritdoc /> public override async Task RunAsync(RuleContext context) { this.Logger.LogTrace("()"); Block block = context.BlockValidationContext.Block; ChainedHeader index = context.BlockValidationContext.ChainedHeader; DeploymentFlags flags = context.Flags; UnspentOutputSet view = context.Set; this.Parent.PerformanceCounter.AddProcessedBlocks(1); long sigOpsCost = 0; Money fees = Money.Zero; var checkInputs = new List <Task <bool> >(); for (int txIndex = 0; txIndex < block.Transactions.Count; txIndex++) { this.Parent.PerformanceCounter.AddProcessedTransactions(1); Transaction tx = block.Transactions[txIndex]; if (!context.SkipValidation) { // TODO: Simplify this condition. if (!tx.IsCoinBase && (!context.IsPoS || (context.IsPoS && !tx.IsCoinStake))) { if (!view.HaveInputs(tx)) { this.Logger.LogTrace("(-)[BAD_TX_NO_INPUT]"); ConsensusErrors.BadTransactionMissingInput.Throw(); } var prevheights = new int[tx.Inputs.Count]; // Check that transaction is BIP68 final. // BIP68 lock checks (as opposed to nLockTime checks) must // be in ConnectBlock because they require the UTXO set. for (int j = 0; j < tx.Inputs.Count; j++) { prevheights[j] = (int)view.AccessCoins(tx.Inputs[j].PrevOut.Hash).Height; } if (!tx.CheckSequenceLocks(prevheights, index, flags.LockTimeFlags)) { this.Logger.LogTrace("(-)[BAD_TX_NON_FINAL]"); ConsensusErrors.BadTransactionNonFinal.Throw(); } } // GetTransactionSignatureOperationCost counts 3 types of sigops: // * legacy (always), // * p2sh (when P2SH enabled in flags and excludes coinbase), // * witness (when witness enabled in flags and excludes coinbase). sigOpsCost += this.GetTransactionSignatureOperationCost(tx, view, flags); if (sigOpsCost > this.consensusOptions.MaxBlockSigopsCost) { this.Logger.LogTrace("(-)[BAD_BLOCK_SIG_OPS]"); ConsensusErrors.BadBlockSigOps.Throw(); } // TODO: Simplify this condition. if (!tx.IsCoinBase && (!context.IsPoS || (context.IsPoS && !tx.IsCoinStake))) { this.CheckInputs(tx, view, index.Height); fees += view.GetValueIn(tx) - tx.TotalOut; var txData = new PrecomputedTransactionData(tx); for (int inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++) { this.Parent.PerformanceCounter.AddProcessedInputs(1); TxIn input = tx.Inputs[inputIndex]; int inputIndexCopy = inputIndex; TxOut txout = view.GetOutputFor(input); var checkInput = new Task <bool>(() => { var checker = new TransactionChecker(tx, inputIndexCopy, txout.Value, txData); var ctx = new ScriptEvaluationContext(this.Parent.Network); ctx.ScriptVerify = flags.ScriptFlags; return(ctx.VerifyScript(input.ScriptSig, txout.ScriptPubKey, checker)); }); checkInput.Start(); checkInputs.Add(checkInput); } } } this.UpdateCoinView(context, tx); } if (!context.SkipValidation) { this.CheckBlockReward(context, fees, index.Height, block); foreach (Task <bool> checkInput in checkInputs) { if (await checkInput.ConfigureAwait(false)) { continue; } this.Logger.LogTrace("(-)[BAD_TX_SCRIPT]"); ConsensusErrors.BadTransactionScriptError.Throw(); } } else { this.Logger.LogTrace("BIP68, SigOp cost, and block reward validation skipped for block at height {0}.", index.Height); } this.Logger.LogTrace("(-)"); }
/// <inheritdoc /> public override Task RunAsync(RuleContext context) { DeploymentFlags deploymentFlags = context.Flags; Block block = context.BlockValidationContext.Block; // 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 fHaveWitness = false; if (deploymentFlags.ScriptFlags.HasFlag(ScriptVerify.Witness)) { int commitpos = this.GetWitnessCommitmentIndex(block); if (commitpos != -1) { uint256 hashWitness = BlockWitnessMerkleRoot(block, out bool malleated); // 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(); } byte[] 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(); } fHaveWitness = true; } } if (!fHaveWitness) { for (int i = 0; i < block.Transactions.Count; i++) { if (block.Transactions[i].HasWitness) { this.Logger.LogTrace("(-)[UNEXPECTED_WITNESS]"); ConsensusErrors.UnexpectedWitness.Throw(); } } } return(Task.CompletedTask); }
/// <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]"); }