public void TallyTransaction(Chain newChain, ValidatableTx validatableTx, ref object runningTally)
{
var chainedHeader = newChain.LastBlock;
if (runningTally == null)
{
var medianPrevHeaderTime = GetMedianPrevHeaderTime(newChain, chainedHeader.Height);
var lockTimeFlags = 0; // TODO why is this used here?
var lockTimeCutoff = ((lockTimeFlags & LOCKTIME_MEDIAN_TIME_PAST) != 0)
? GetMedianPrevHeaderTime(newChain, chainedHeader.Height).ToUnixTimeSeconds()
: chainedHeader.Time.ToUnixTimeSeconds();
runningTally = new BlockTally { BlockSize = 80, LockTimeCutoff = lockTimeCutoff };
}
var blockTally = (BlockTally)runningTally;
var tx = validatableTx.Transaction;
var txIndex = validatableTx.Index;
// BIP16 didn't become active until Apr 1 2012
var nBIP16SwitchTime = DateTimeOffset.FromUnixTimeSeconds(1333238400U);
var strictPayToScriptHash = chainedHeader.Time >= nBIP16SwitchTime;
// first transaction must be coinbase
if (validatableTx.Index == 0 && !tx.IsCoinbase)
throw new ValidationException(chainedHeader.Hash);
// all other transactions must not be coinbase
else if (validatableTx.Index > 0 && tx.IsCoinbase)
throw new ValidationException(chainedHeader.Hash);
// must have inputs
else if (tx.Inputs.Length == 0)
throw new ValidationException(chainedHeader.Hash);
// must have outputs
else if (tx.Outputs.Length == 0)
throw new ValidationException(chainedHeader.Hash);
// coinbase scriptSignature length must be >= 2 && <= 100
else if (tx.IsCoinbase && (tx.Inputs[0].ScriptSignature.Length < 2 || tx.Inputs[0].ScriptSignature.Length > 100))
throw new ValidationException(chainedHeader.Hash);
// all transactions must be finalized
else if (!IsFinal(tx, chainedHeader.Height, blockTally.LockTimeCutoff))
throw new ValidationException(chainedHeader.Hash);
// Enforce block.nVersion=2 rule that the coinbase starts with serialized block height
// if 750 of the last 1,000 blocks are version 2 or greater (51/100 if testnet):
if (tx.IsCoinbase
&& chainedHeader.Version >= 2
&& IsSuperMajority(2, newChain, ChainParams.MajorityEnforceBlockUpgrade))
{
var requiredScript = GetPushInt64Script(chainedHeader.Height);
var actualScript = tx.Inputs[0].ScriptSignature;
if (actualScript.Length < requiredScript.Length
|| !actualScript.Take(requiredScript.Length).SequenceEqual(requiredScript))
{
throw new ValidationException(chainedHeader.Hash);
}
}
// running input/output value tallies
var txTotalInputValue = 0UL;
var txTotalOutputValue = 0UL;
var txSigOpCount = 0;
// validate & tally inputs
for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++)
{
var input = tx.Inputs[inputIndex];
if (!tx.IsCoinbase)
{
var prevOutput = validatableTx.PrevTxOutputs[inputIndex];
// if spending a coinbase, it must be mature
if (prevOutput.IsCoinbase
&& chainedHeader.Height - prevOutput.BlockHeight < COINBASE_MATURITY)
{
throw new ValidationException(chainedHeader.Hash);
}
// non-coinbase txes must not have coinbase prev tx output key (txHash: 0, outputIndex: -1)
if (input.PrevTxOutputKey.TxOutputIndex == uint.MaxValue && input.PrevTxOutputKey.TxHash == UInt256.Zero)
throw new ValidationException(chainedHeader.Hash);
// tally
txTotalInputValue += prevOutput.Value;
}
// tally
txSigOpCount += CountLegacySigOps(input.ScriptSignature, accurate: false);
if (!tx.IsCoinbase && strictPayToScriptHash)
txSigOpCount += CountP2SHSigOps(validatableTx, inputIndex);
}
// validate & tally outputs
for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++)
{
var output = tx.Outputs[outputIndex];
// must not have any negative money value outputs
if (unchecked((long)output.Value) < 0)
throw new ValidationException(chainedHeader.Hash);
// must not have any outputs with a value greater than max money
else if (output.Value > MAX_MONEY)
throw new ValidationException(chainedHeader.Hash);
// tally
if (!tx.IsCoinbase)
txTotalOutputValue += output.Value;
txSigOpCount += CountLegacySigOps(output.ScriptPublicKey, accurate: false);
}
// must not have a total output value greater than max money
if (txTotalOutputValue > MAX_MONEY)
throw new ValidationException(chainedHeader.Hash);
// validate non-coinbase fees
long txFeeValue;
if (!validatableTx.IsCoinbase)
{
// ensure that output amount isn't greater than input amount
if (txTotalOutputValue > txTotalInputValue)
throw new ValidationException(chainedHeader.Hash, $"Failing tx {tx.Hash}: Transaction output value is greater than input value");
// calculate fee value (unspent amount)
txFeeValue = (long)txTotalInputValue - (long)txTotalOutputValue;
Debug.Assert(txFeeValue >= 0);
}
else
txFeeValue = 0;
// block tallies
if (validatableTx.IsCoinbase)
blockTally.CoinbaseTx = validatableTx;
blockTally.TxCount++;
blockTally.TotalFees += txFeeValue;
blockTally.TotalSigOpCount += txSigOpCount;
// re-encode transaction for block size calculation so it is optimal length
blockTally.BlockSize +=
DataEncoder.EncodeTransaction(validatableTx.Transaction).TxBytes.Length;
//TODO
if (blockTally.TotalSigOpCount > MAX_BLOCK_SIGOPS)
throw new ValidationException(chainedHeader.Hash);
//TODO
else if (blockTally.BlockSize + DataEncoder.VarIntSize((uint)blockTally.TxCount) > MAX_BLOCK_SIZE)
throw new ValidationException(chainedHeader.Hash);
// all validation has passed
}