/// <inheritdoc /> public async override Task RunAsync(RuleContext context) { this.blockTxsProcessed = new List<Transaction>(); NBitcoin.Block block = context.ValidationContext.BlockToValidate; ChainedHeader index = context.ValidationContext.ChainedHeaderToValidate; DeploymentFlags flags = context.Flags; UnspentOutputSet view = ((UtxoRuleContext)context).UnspentOutputSet; // Start state from previous block's root this.ContractCoinviewRule.OriginalStateRoot.SyncToRoot(((SmartContractBlockHeader)context.ValidationContext.ChainedHeaderToValidate.Previous.Header).HashStateRoot.ToBytes()); IStateRepository 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++) { 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++) { 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(); }
public virtual void ExecuteBlock(ContextInformation context, TaskScheduler taskScheduler) { Block block = context.BlockResult.Block; ChainedBlock index = context.BlockResult.ChainedBlock; ConsensusFlags flags = context.Flags; UnspentOutputSet view = context.Set; this.PerformanceCounter.AddProcessedBlocks(1); taskScheduler = taskScheduler ?? TaskScheduler.Default; if (flags.EnforceBIP30) { foreach (var tx in block.Transactions) { var coins = view.AccessCoins(tx.GetHash()); if (coins != null && !coins.IsPrunable) { ConsensusErrors.BadTransactionBIP30.Throw(); } } } long nSigOpsCost = 0; Money nFees = Money.Zero; List <Task <bool> > checkInputs = new List <Task <bool> >(); for (int i = 0; i < block.Transactions.Count; i++) { this.PerformanceCounter.AddProcessedTransactions(1); var tx = block.Transactions[i]; if (!tx.IsCoinBase && (!context.IsPoS || (context.IsPoS && !tx.IsCoinStake))) { int[] prevheights; if (!view.HaveInputs(tx)) { 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 (var 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)) { 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.MAX_BLOCK_SIGOPS_COST) { ConsensusErrors.BadBlockSigOps.Throw(); } if (!tx.IsCoinBase && (!context.IsPoS || (context.IsPoS && !tx.IsCoinStake))) { this.CheckInputs(tx, view, index.Height); nFees += view.GetValueIn(tx) - tx.TotalOut; int ii = i; var localTx = tx; PrecomputedTransactionData txData = new PrecomputedTransactionData(tx); for (int iInput = 0; iInput < tx.Inputs.Count; iInput++) { this.PerformanceCounter.AddProcessedInputs(1); var input = tx.Inputs[iInput]; int iiIntput = iInput; var txout = view.GetOutputFor(input); var checkInput = new Task <bool>(() => { if (this.UseConsensusLib) { Script.BitcoinConsensusError error; return(Script.VerifyScriptConsensus(txout.ScriptPubKey, tx, (uint)iiIntput, flags.ScriptFlags, out error)); } else { var checker = new TransactionChecker(tx, iiIntput, 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); } this.CheckBlockReward(context, nFees, index, block); var passed = checkInputs.All(c => c.GetAwaiter().GetResult()); if (!passed) { ConsensusErrors.BadTransactionScriptError.Throw(); } }
private static Script CombineMultisig(Script scriptPubKey, TransactionChecker checker, byte[][] sigs1, byte[][] sigs2, HashVersion hashVersion) { // Combine all the signatures we've got: List<TransactionSignature> allsigs = new List<TransactionSignature>(); foreach(var v in sigs1) { if(TransactionSignature.IsValid(v)) { allsigs.Add(new TransactionSignature(v)); } } foreach(var v in sigs2) { if(TransactionSignature.IsValid(v)) { allsigs.Add(new TransactionSignature(v)); } } var multiSigParams = PayToMultiSigTemplate.Instance.ExtractScriptPubKeyParameters(scriptPubKey); if(multiSigParams == null) throw new InvalidOperationException("The scriptPubKey is not a valid multi sig"); Dictionary<PubKey, TransactionSignature> sigs = new Dictionary<PubKey, TransactionSignature>(); foreach(var sig in allsigs) { foreach(var pubkey in multiSigParams.PubKeys) { if(sigs.ContainsKey(pubkey)) continue; // Already got a sig for this pubkey ScriptEvaluationContext eval = new ScriptEvaluationContext(); if(eval.CheckSig(sig, pubkey, scriptPubKey, checker, hashVersion)) { sigs.AddOrReplace(pubkey, sig); } } } // Now build a merged CScript: int nSigsHave = 0; Script result = new Script(OpcodeType.OP_0); // pop-one-too-many workaround foreach(var pubkey in multiSigParams.PubKeys) { if(sigs.ContainsKey(pubkey)) { result += Op.GetPushOp(sigs[pubkey].ToBytes()); nSigsHave++; } if(nSigsHave >= multiSigParams.SignatureCount) break; } // Fill any missing with OP_0: for(int i = nSigsHave; i < multiSigParams.SignatureCount; i++) result += OpcodeType.OP_0; return result; }
private static Script CombineSignatures(Script scriptPubKey, TransactionChecker checker, byte[][] sigs1, byte[][] sigs2, HashVersion hashVersion) { var template = StandardScripts.GetTemplateFromScriptPubKey(scriptPubKey); if(template is PayToWitPubKeyHashTemplate) { scriptPubKey = new KeyId(scriptPubKey.ToBytes(true).SafeSubarray(1, 20)).ScriptPubKey; template = StandardScripts.GetTemplateFromScriptPubKey(scriptPubKey); } if(template == null || template is TxNullDataTemplate) return PushAll(Max(sigs1, sigs2)); if(template is PayToPubkeyTemplate || template is PayToPubkeyHashTemplate) if(sigs1.Length == 0 || sigs1[0].Length == 0) return PushAll(sigs2); else return PushAll(sigs1); if(template is PayToScriptHashTemplate || template is PayToWitTemplate) { if(sigs1.Length == 0 || sigs1[sigs1.Length - 1].Length == 0) return PushAll(sigs2); if(sigs2.Length == 0 || sigs2[sigs2.Length - 1].Length == 0) return PushAll(sigs1); var redeemBytes = sigs1[sigs1.Length - 1]; var redeem = new Script(redeemBytes); sigs1 = sigs1.Take(sigs1.Length - 1).ToArray(); sigs2 = sigs2.Take(sigs2.Length - 1).ToArray(); Script result = CombineSignatures(redeem, checker, sigs1, sigs2, hashVersion); result += Op.GetPushOp(redeemBytes); return result; } if(template is PayToMultiSigTemplate) { return CombineMultisig(scriptPubKey, checker, sigs1, sigs2, hashVersion); } return null; }
private static Script CombineMultisig(Script scriptPubKey, TransactionChecker checker, byte[][] sigs1, byte[][] sigs2, HashVersion hashVersion) { // Combine all the signatures we've got: List<TransactionSignature> allsigs = new List<TransactionSignature>(); foreach(var v in sigs1) { if(TransactionSignature.IsValid(v)) { allsigs.Add(new TransactionSignature(v)); } } foreach(var v in sigs2) { if(TransactionSignature.IsValid(v)) { allsigs.Add(new TransactionSignature(v)); } } var multiSigParams = PayToMultiSigTemplate.Instance.ExtractScriptPubKeyParameters(scriptPubKey); if(multiSigParams == null) throw new InvalidOperationException("The scriptPubKey is not a valid multi sig"); Dictionary<PubKey, TransactionSignature> sigs = new Dictionary<PubKey, TransactionSignature>(); foreach(var sig in allsigs) { foreach(var pubkey in multiSigParams.PubKeys) { if(sigs.ContainsKey(pubkey)) continue; // Already got a sig for this pubkey ScriptEvaluationContext eval = new ScriptEvaluationContext(); if(eval.CheckSig(sig, pubkey, scriptPubKey, checker, hashVersion)) { sigs.AddOrReplace(pubkey, sig); } } } // Now build a merged CScript: int nSigsHave = 0; Script result = new Script(OpcodeType.OP_0); // pop-one-too-many workaround foreach(var pubkey in multiSigParams.PubKeys) { if(sigs.ContainsKey(pubkey)) { result += Op.GetPushOp(sigs[pubkey].ToBytes()); nSigsHave++; } if(nSigsHave >= multiSigParams.SignatureCount) break; } // Fill any missing with OP_0: for(int i = nSigsHave ; i < multiSigParams.SignatureCount ; i++) result += OpcodeType.OP_0; return result; }
public static ScriptSigs CombineSignatures(Script scriptPubKey, TransactionChecker checker, ScriptSigs input1, ScriptSigs input2) { if(scriptPubKey == null) scriptPubKey = new Script(); var scriptSig1 = input1.ScriptSig; var scriptSig2 = input2.ScriptSig; HashVersion hashVersion = HashVersion.Original; var isWitness = input1.WitSig != WitScript.Empty || input2.WitSig != WitScript.Empty; if(isWitness) { scriptSig1 = input1.WitSig.ToScript(); scriptSig2 = input2.WitSig.ToScript(); hashVersion = HashVersion.Witness; } var context = new ScriptEvaluationContext(); context.ScriptVerify = ScriptVerify.StrictEnc; context.EvalScript(scriptSig1, checker, hashVersion); var stack1 = context.Stack.AsInternalArray(); context = new ScriptEvaluationContext(); context.ScriptVerify = ScriptVerify.StrictEnc; context.EvalScript(scriptSig2, checker, hashVersion); var stack2 = context.Stack.AsInternalArray(); var result = CombineSignatures(scriptPubKey, checker, stack1, stack2, hashVersion); if(result == null) return scriptSig1.Length < scriptSig2.Length ? input2 : input1; if(!isWitness) return new ScriptSigs() { ScriptSig = result, WitSig = WitScript.Empty }; else { return new ScriptSigs() { ScriptSig = input1.ScriptSig.Length < input2.ScriptSig.Length ? input2.ScriptSig : input1.ScriptSig, WitSig = new WitScript(result) }; } }
/// <inheritdoc /> public override async Task RunAsync(RuleContext context) { this.Logger.LogTrace("()"); Block block = context.ValidationContext.Block; ChainedHeader index = context.ValidationContext.ChainedHeader; DeploymentFlags flags = context.Flags; UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet; 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) { 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.PowConsensusOptions.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++) { 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 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("(-)"); }
// Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts) // This does not modify the UTXO set. If pvChecks is not NULL, script checks are pushed onto it // instead of being performed inline. private bool CheckInputs(MempoolValidationContext context, ScriptVerify scriptVerify, PrecomputedTransactionData txData) { var tx = context.Transaction; if (!context.Transaction.IsCoinBase) { this.consensusValidator.CheckInputs(context.Transaction, context.View.Set, this.chain.Height + 1); for (int iInput = 0; iInput < tx.Inputs.Count; iInput++) { var input = tx.Inputs[iInput]; int iiIntput = iInput; var txout = context.View.GetOutputFor(input); if (this.consensusValidator.UseConsensusLib) { Script.BitcoinConsensusError error; return(Script.VerifyScriptConsensus(txout.ScriptPubKey, tx, (uint)iiIntput, scriptVerify, out error)); } else { var checker = new TransactionChecker(tx, iiIntput, txout.Value, txData); var ctx = new ScriptEvaluationContext(); ctx.ScriptVerify = scriptVerify; if (ctx.VerifyScript(input.ScriptSig, txout.ScriptPubKey, checker)) { return(true); } else { //TODO: //if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) //{ // // Check whether the failure was caused by a // // non-mandatory script verification check, such as // // non-standard DER encodings or non-null dummy // // arguments; if so, don't trigger DoS protection to // // avoid splitting the network between upgraded and // // non-upgraded nodes. // CScriptCheck check2(*coins, tx, i, // flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &txdata); // if (check2()) // return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); //} //// Failures of other flags indicate a transaction that is //// invalid in new blocks, e.g. a invalid P2SH. We DoS ban //// such nodes as they are not following the protocol. That //// said during an upgrade careful thought should be taken //// as to the correct behavior - we may want to continue //// peering with non-upgraded nodes even after soft-fork //// super-majority signaling has occurred. context.State.Fail(MempoolErrors.MandatoryScriptVerifyFlagFailed, ctx.Error.ToString()).Throw(); } } } } return(true); }
/// <summary> /// Validates transaction inputs against transaction data for a specific set of script verify flags. /// Check whether all inputs of this transaction are valid (no double spends, scripts & signatures, amounts) /// This does not modify the UTXO set. /// </summary> /// <seealso>https://github.com/bitcoin/bitcoin/blob/febf3a856bcfb8fef2cb4ddcb8d1e0cab8a22580/src/validation.cpp#L1259</seealso> /// <param name="ruleContext">Current mempool rule context.</param> /// <param name="context">Current validation context.</param> /// <param name="scriptVerify">Script verify flag.</param> /// <param name="txData">Transaction data.</param> /// <returns>Whether inputs are valid.</returns> private bool CheckInputs(MempoolValidationContext context, ScriptVerify scriptVerify, PrecomputedTransactionData txData) { Transaction tx = context.Transaction; if (tx.IsCoinBase) { return(true); } // TODO: The original code does not appear to do these checks here. Reevaluate if this needs to be done, or perhaps moved to another rule/method. this.consensusRuleEngine.GetRule <CoinViewRule>().CheckInputs(context.Transaction, context.View.Set, this.chainIndexer.Height + 1); // TODO: Original code has the concept of a script execution cache. This might be worth looking into for performance improvements. Signature checks are expensive. for (int iInput = 0; iInput < tx.Inputs.Count; iInput++) { TxIn input = tx.Inputs[iInput]; int iiInput = iInput; TxOut txout = context.View.GetOutputFor(input); var checker = new TransactionChecker(tx, iiInput, txout.Value, txData); var ctx = new ScriptEvaluationContext(this.network) { ScriptVerify = scriptVerify }; if (!ctx.VerifyScript(input.ScriptSig, txout.ScriptPubKey, checker)) { if ((scriptVerify & ScriptVerify.StandardNotMandatory) == ScriptVerify.StandardNotMandatory) { // Check whether the failure was caused by a non-mandatory script verification check, such as // non-standard DER encodings or non-null dummy arguments; if so, don't trigger DoS protection to // avoid splitting the network between upgraded and non-upgraded nodes. // TODO: Investigate whether the checker and context can be reused instead of recreated. Probably not. checker = new TransactionChecker(tx, iiInput, txout.Value, txData); ctx = new ScriptEvaluationContext(this.network) { ScriptVerify = (scriptVerify & ~ScriptVerify.StandardNotMandatory) }; if (ctx.VerifyScript(input.ScriptSig, txout.ScriptPubKey, checker)) { this.logger.LogTrace("(-)[FAIL_NON_MANDATORY_SCRIPT_VERIFY]"); this.logger.LogDebug("Failed non-mandatory script verify: Transaction = {0}, scriptVerify = {1}, ctx.scriptVerify = {2}", tx.ToHex(), scriptVerify, ctx.ScriptVerify); // TODO: Check what this actually means in Core's logic. If it is on testnet/regtest and RequireStandard is false, is the transaction still rejected? context.State.Fail(MempoolErrors.NonMandatoryScriptVerifyFlagFailed, ctx.Error.ToString()).Throw(); } } // Failures of other flags indicate a transaction that is invalid in new blocks, e.g. an invalid P2SH. We DoS ban // such nodes as they are not following the protocol. That said, during an upgrade careful thought should be taken // as to the correct behavior - we may want to continue peering with non-upgraded nodes even after soft-fork // super-majority signaling has occurred. // Further comment from Bitcoin Core: // MANDATORY flag failures correspond to // ValidationInvalidReason::CONSENSUS. Because CONSENSUS // failures are the most serious case of validation // failures, we may need to consider using // RECENT_CONSENSUS_CHANGE for any script failure that // could be due to non-upgraded nodes which we may want to // support, to avoid splitting the network (but this // depends on the details of how net_processing handles // such errors). this.logger.LogTrace("(-)[FAIL_MANDATORY_SCRIPT_VERIFY]"); context.State.Fail(MempoolErrors.MandatoryScriptVerifyFlagFailed, ctx.Error.ToString()).Throw(); } } return(true); }