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
        }