public override void ValidateTransaction(ChainedHeader chainedHeader, LoadedTx loadedTx) { if (ValidateTransactionAction == null) base.ValidateTransaction(chainedHeader, loadedTx); else ValidateTransactionAction(chainedHeader, loadedTx); }
//TODO //public virtual void ValidateBlock(ChainedBlock chainedBlock, ChainStateBuilder chainStateBuilder) //{ // //TODO // if (BypassValidation) // return; // // calculate the next required target // var requiredTarget = GetRequiredNextTarget(chainStateBuilder.Chain); // // validate block's target against the required target // var blockTarget = chainedBlock.Header.CalculateTarget(); // if (blockTarget > requiredTarget) // { // throw new ValidationException(chainedBlock.Hash, "Failing block {0} at height {1}: Block target {2} did not match required target of {3}".Format2(chainedBlock.Hash.ToHexNumberString(), chainedBlock.Height, blockTarget.ToHexNumberString(), requiredTarget.ToHexNumberString())); // } // // validate block's proof of work against its stated target // if (chainedBlock.Hash > blockTarget || chainedBlock.Hash > requiredTarget) // { // throw new ValidationException(chainedBlock.Hash, "Failing block {0} at height {1}: Block did not match its own target of {2}".Format2(chainedBlock.Hash.ToHexNumberString(), chainedBlock.Height, blockTarget.ToHexNumberString())); // } // // ensure there is at least 1 transaction // if (chainedBlock.Transactions.Length == 0) // { // throw new ValidationException(chainedBlock.Hash, "Failing block {0} at height {1}: Zero transactions present".Format2(chainedBlock.Hash.ToHexNumberString(), chainedBlock.Height)); // } // //TODO apply real coinbase rule // // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219 // var coinbaseTx = chainedBlock.Transactions[0]; // // check that coinbase has only one input // if (coinbaseTx.Inputs.Length != 1) // { // throw new ValidationException(chainedBlock.Hash, "Failing block {0} at height {1}: Coinbase transaction does not have exactly one input".Format2(chainedBlock.Hash.ToHexNumberString(), chainedBlock.Height)); // } // var blockTxIndices = new Dictionary<UInt256, int>(); // for (var i = 0; i < chainedBlock.Transactions.Length; i++) // blockTxIndices.Add(chainedBlock.Transactions[i].Hash, i); // // validate transactions // long blockUnspentValue = 0L; // for (var txIndex = 1; txIndex < chainedBlock.Transactions.Length; txIndex++) // { // var tx = chainedBlock.Transactions[txIndex]; // long txUnspentValue; // ValidateTransaction(chainedBlock, tx, txIndex, chainStateBuilder, out txUnspentValue, blockTxIndices); // blockUnspentValue += txUnspentValue; // } // // calculate the expected reward in coinbase // var expectedReward = (long)(50 * SATOSHI_PER_BTC); // if (chainedBlock.Height / 210000 <= 32) // expectedReward /= (long)Math.Pow(2, chainedBlock.Height / 210000); // expectedReward += blockUnspentValue; // // calculate the actual reward in coinbase // var actualReward = 0L; // foreach (var txOutput in coinbaseTx.Outputs) // actualReward += (long)txOutput.Value; // // ensure coinbase has correct reward // if (actualReward > expectedReward) // { // throw new ValidationException(chainedBlock.Hash, "Failing block {0} at height {1}: Coinbase value is greater than reward + fees".Format2(chainedBlock.Hash.ToHexNumberString(), chainedBlock.Height)); // } // // all validation has passed //} public virtual void ValidateTransaction(ChainedHeader chainedHeader, LoadedTx loadedTx) { var tx = loadedTx.Transaction; var txIndex = loadedTx.TxIndex; if (loadedTx.IsCoinbase) { // TODO coinbase tx validation } else { // verify spend amounts var txInputValue = (UInt64)0; var txOutputValue = (UInt64)0; for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; var prevOutput = loadedTx.GetInputPrevTxOutput(inputIndex); // add transactions previous value to unspent amount (used to calculate allowed coinbase reward) txInputValue += prevOutput.Value; } for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++) { // remove transactions spend value from unspent amount (used to calculate allowed coinbase reward) var output = tx.Outputs[outputIndex]; txOutputValue += output.Value; } // ensure that amount being output from transaction isn't greater than amount being input if (txOutputValue > txInputValue) { throw new ValidationException(chainedHeader.Hash, $"Failing tx {tx.Hash}: Transaction output value is greater than input value"); } // calculate fee value (unspent amount) var feeValue = (long)(txInputValue - txOutputValue); // sanity check if (feeValue < 0) { throw new ValidationException(chainedHeader.Hash); } } // all validation has passed }