/// <summary> /// Checks that transaction's inputs are valid. /// </summary> /// <param name="transaction">Transaction to check.</param> /// <param name="inputs">Map of previous transactions that have outputs we're spending.</param> /// <param name="spendHeight">Height at which we are spending coins.</param> /// <exception cref="ConsensusErrors.BadTransactionMissingInput">Thrown if transaction's inputs are missing.</exception> /// <exception cref="ConsensusErrors.BadTransactionInputValueOutOfRange">Thrown if input value is out of range.</exception> /// <exception cref="ConsensusErrors.BadTransactionInBelowOut">Thrown if transaction inputs are less then outputs.</exception> /// <exception cref="ConsensusErrors.BadTransactionNegativeFee">Thrown if fees sum is negative.</exception> /// <exception cref="ConsensusErrors.BadTransactionFeeOutOfRange">Thrown if fees value is out of range.</exception> public void CheckInputs(Transaction transaction, UnspentOutputSet inputs, int spendHeight) { this.Logger.LogTrace("({0}:{1})", nameof(spendHeight), spendHeight); if (!inputs.HaveInputs(transaction)) { ConsensusErrors.BadTransactionMissingInput.Throw(); } Money valueIn = Money.Zero; Money fees = Money.Zero; for (int i = 0; i < transaction.Inputs.Count; i++) { OutPoint prevout = transaction.Inputs[i].PrevOut; UnspentOutputs coins = inputs.AccessCoins(prevout.Hash); this.CheckMaturity(coins, spendHeight); // Check for negative or overflow input values. valueIn += coins.TryGetOutput(prevout.N).Value; if (!this.MoneyRange(coins.TryGetOutput(prevout.N).Value) || !this.MoneyRange(valueIn)) { this.Logger.LogTrace("(-)[BAD_TX_INPUT_VALUE]"); ConsensusErrors.BadTransactionInputValueOutOfRange.Throw(); } } if (valueIn < transaction.TotalOut) { this.Logger.LogTrace("(-)[TX_IN_BELOW_OUT]"); ConsensusErrors.BadTransactionInBelowOut.Throw(); } // Tally transaction fees. Money txFee = valueIn - transaction.TotalOut; if (txFee < 0) { this.Logger.LogTrace("(-)[NEGATIVE_FEE]"); ConsensusErrors.BadTransactionNegativeFee.Throw(); } fees += txFee; if (!this.MoneyRange(fees)) { this.Logger.LogTrace("(-)[BAD_FEE]"); ConsensusErrors.BadTransactionFeeOutOfRange.Throw(); } this.Logger.LogTrace("(-)"); }
/// <summary> /// Checks that transaction's inputs are valid. /// </summary> /// <param name="transaction">Transaction to check.</param> /// <param name="inputs">Map of previous transactions that have outputs we're spending.</param> /// <param name="spendHeight">Height at which we are spending coins.</param> /// <exception cref="ConsensusErrors.BadTransactionMissingInput">Thrown if transaction's inputs are missing.</exception> /// <exception cref="ConsensusErrors.BadTransactionInputValueOutOfRange">Thrown if input value is out of range.</exception> /// <exception cref="ConsensusErrors.BadTransactionInBelowOut">Thrown if transaction inputs are less then outputs.</exception> /// <exception cref="ConsensusErrors.BadTransactionNegativeFee">Thrown if fees sum is negative.</exception> /// <exception cref="ConsensusErrors.BadTransactionFeeOutOfRange">Thrown if fees value is out of range.</exception> public void CheckInputs(Transaction transaction, UnspentOutputSet inputs, int spendHeight) { if (!inputs.HaveInputs(transaction)) { ConsensusErrors.BadTransactionMissingInput.Throw(); } var valueIn = Money.Zero; var fees = Money.Zero; for (var i = 0; i < transaction.Inputs.Count; i++) { var prevout = transaction.Inputs[i].PrevOut; var coins = inputs.AccessCoins(prevout.Hash); CheckMaturity(coins, spendHeight); CheckInputValidity(transaction, coins); // Check for negative or overflow input values. valueIn += coins.TryGetOutput(prevout.N).Value; if (!MoneyRange(coins.TryGetOutput(prevout.N).Value) || !MoneyRange(valueIn)) { this.Logger.LogTrace("(-)[BAD_TX_INPUT_VALUE]"); ConsensusErrors.BadTransactionInputValueOutOfRange.Throw(); } } if (!transaction.IsProtocolTransaction()) { if (valueIn < transaction.TotalOut) { this.Logger.LogTrace("(-)[TX_IN_BELOW_OUT]"); ConsensusErrors.BadTransactionInBelowOut.Throw(); } // Check transaction fees. var txFee = valueIn - transaction.TotalOut; if (txFee < 0) { this.Logger.LogTrace("(-)[NEGATIVE_FEE]"); ConsensusErrors.BadTransactionNegativeFee.Throw(); } fees += txFee; if (!MoneyRange(fees)) { this.Logger.LogTrace("(-)[BAD_FEE]"); ConsensusErrors.BadTransactionFeeOutOfRange.Throw(); } } }
/// <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.LogTrace("Transaction '{0}' has not inputs", tx.GetHash()); this.Logger.LogTrace("(-)[BAD_TX_NO_INPUT]"); ConsensusErrors.BadTransactionMissingInput.Throw(); } if (!this.IsTxFinal(tx, context)) { this.Logger.LogTrace("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); if (!tx.IsCoinStake) { fees += view.GetValueIn(tx) - tx.TotalOut; } var txData = new PrecomputedTransactionData(tx); for (int inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++) { TxIn input = tx.Inputs[inputIndex]; inputsToCheck.Add(( tx: tx, inputIndexCopy: inputIndex, txOut: view.GetOutputFor(input), 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.LogTrace("BIP68, SigOp cost, and block reward validation skipped for block at height {0}.", index.Height); } }
/// <inheritdoc /> public async override Task RunAsync(RuleContext context) { this.Logger.LogTrace("()"); this.blockTxsProcessed = new List <Transaction>(); NBitcoin.Block block = context.ValidationContext.Block; ChainedHeader index = context.ValidationContext.ChainedHeader; DeploymentFlags flags = context.Flags; UnspentOutputSet view = ((UtxoRuleContext)context).UnspentOutputSet; this.Parent.PerformanceCounter.AddProcessedBlocks(1); // Start state from previous block's root this.ContractCoinviewRule.OriginalStateRoot.SyncToRoot(((SmartContractBlockHeader)context.ConsensusTip.Header).HashStateRoot.ToBytes()); IContractState trackedState = this.ContractCoinviewRule.OriginalStateRoot.StartTracking(); this.receipts = new List <Receipt>(); this.refundCounter = 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) { if (!this.IsProtocolTransaction(tx)) { 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) { ConsensusErrors.BadBlockSigOps.Throw(); } if (!this.IsProtocolTransaction(tx)) { 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>(() => { if (txout.ScriptPubKey.IsSmartContractExec() || txout.ScriptPubKey.IsSmartContractInternalCall()) { return(input.ScriptSig.IsSmartContractSpend()); } 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); this.blockTxsProcessed.Add(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); } if (new uint256(this.ContractCoinviewRule.OriginalStateRoot.Root) != ((SmartContractBlockHeader)block.Header).HashStateRoot) { SmartContractConsensusErrors.UnequalStateRoots.Throw(); } ValidateAndStoreReceipts(((SmartContractBlockHeader)block.Header).ReceiptRoot); this.ContractCoinviewRule.OriginalStateRoot.Commit(); this.Logger.LogTrace("(-)"); }
/// <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 checkInputs = new List <Task <bool> >(); for (int txIndex = 0; txIndex < block.Transactions.Count; txIndex++) { Transaction tx = block.Transactions[txIndex]; if (!context.SkipValidation) { if (!this.IsProtocolTransaction(tx)) { 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(); } if (!this.IsProtocolTransaction(tx)) { 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++) { TxIn input = tx.Inputs[inputIndex]; int inputIndexCopy = inputIndex; TxOut txout = view.GetOutputFor(input); var checkInput = new Task <bool>(() => this.CheckInput(tx, inputIndexCopy, txout, txData, input, flags)); 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); } }
/// <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.PowConsensusOptions.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; bool verifyScriptResult = ctx.VerifyScript(input.ScriptSig, txout.ScriptPubKey, checker); if (verifyScriptResult == false) { this.Logger.LogTrace("Verify script for transaction '{0}' failed, ScriptSig = '{1}', ScriptPubKey = '{2}', script evaluation error = '{3}'", tx.GetHash(), input.ScriptSig, txout.ScriptPubKey, ctx.Error); } return(verifyScriptResult); }); 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 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 checkInputs = new List <Task <bool> >(); 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.LogTrace("Transaction '{0}' has not inputs", tx.GetHash()); this.Logger.LogTrace("(-)[BAD_TX_NO_INPUT]"); ConsensusErrors.BadTransactionMissingInput.Throw(); } if (!this.IsTxFinal(tx, context)) { this.Logger.LogTrace("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); if (!tx.IsCoinStake) { fees += view.GetValueIn(tx) - tx.TotalOut; } var txData = new PrecomputedTransactionData(tx); for (int inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++) { TxIn input = tx.Inputs[inputIndex]; int inputIndexCopy = inputIndex; TxOut txout = view.GetOutputFor(input); var checkInput = new Task <bool>(() => this.CheckInput(tx, inputIndexCopy, txout, txData, input, flags)); 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); } }