/// <inheritdoc />> /// <exception cref="ConsensusErrors.BadTransactionBIP30"> Thrown if BIP30 is not passed.</exception> public override Task RunAsync(RuleContext context) { if (!context.SkipValidation) { Block block = context.BlockValidationContext.Block; DeploymentFlags flags = context.Flags; UnspentOutputSet view = context.Set; 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}.", context.BlockValidationContext.ChainedHeader.Height); return Task.CompletedTask; }
/// <inheritdoc />> /// <exception cref="ConsensusErrors.BadTransactionBIP30"> Thrown if BIP30 is not passed.</exception> public override Task RunAsync(RuleContext context) { if (!context.SkipValidation) { Block block = context.ValidationContext.BlockToValidate; DeploymentFlags flags = context.Flags; var utxoRuleContext = context as UtxoRuleContext; UnspentOutputSet view = utxoRuleContext.UnspentOutputSet; if (flags.EnforceBIP30) { foreach (Transaction tx in block.Transactions) { foreach (IndexedTxOut indexedTxOut in tx.Outputs.AsIndexedOutputs()) { UnspentOutput coins = view.AccessCoins(indexedTxOut.ToOutPoint()); if ((coins?.Coins != null) && !coins.Coins.IsPrunable) { this.Logger.LogDebug("Transaction '{0}' already found in store", tx.GetHash()); this.Logger.LogTrace("(-)[BAD_TX_BIP_30]"); ConsensusErrors.BadTransactionBIP30.Throw(); } } } } } else { this.Logger.LogDebug("BIP30 validation skipped for checkpointed block at height {0}.", context.ValidationContext.ChainedHeaderToValidate.Height); } return(Task.CompletedTask); }
/// <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(); } 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); //Only check output's that have value, not all blocks will have a reward. if (coins.TryGetOutput(prevout.N).Value > Money.Zero) { this.CheckMaturity(coins, spendHeight); } this.CheckInputValidity(transaction, coins); // 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 (!transaction.IsProtocolTransaction()) { if (valueIn < transaction.TotalOut) { this.Logger.LogTrace("(-)[TX_IN_BELOW_OUT]"); ConsensusErrors.BadTransactionInBelowOut.Throw(); } // Check 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(); } } }
public static Money GetFee(this Transaction transaction, UnspentOutputSet inputs) { Money valueIn = Money.Zero; for (int i = 0; i < transaction.Inputs.Count; i++) { OutPoint prevout = transaction.Inputs[i].PrevOut; UnspentOutputs coins = inputs.AccessCoins(prevout.Hash); valueIn += coins.TryGetOutput(prevout.N).Value; } return(valueIn - transaction.TotalOut); }
/// <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 virtual 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("(-)"); }
/// <inheritdoc /> protected override bool IsTxFinal(Transaction transaction, RuleContext context) { if (transaction.IsCoinBase) { return(true); } ChainedHeader index = context.ValidationContext.ChainedHeaderToValidate; UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet; var prevheights = new int[transaction.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 i = 0; i < transaction.Inputs.Count; i++) { prevheights[i] = (int)view.AccessCoins(transaction.Inputs[i].PrevOut.Hash).Height; } return(transaction.CheckSequenceLocks(prevheights, index, context.Flags.LockTimeFlags)); }
/// <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("(-)"); }