public void ValidateTransaction(Chain newChain, ValidatableTx validatableTx) { if (ValidateTransactionAction == null) { coreRules.ValidateTransaction(newChain, validatableTx); } else { ValidateTransactionAction(newChain, validatableTx); } }
public void TallyTransaction(Chain newChain, ValidatableTx validatableTx, ref object runningTally) { if (TallyTransactionFunc == null) { coreRules.TallyTransaction(newChain, validatableTx, ref runningTally); } else { runningTally = TallyTransactionFunc(newChain, validatableTx, runningTally); } }
private int CountP2SHSigOps(ValidatableTx validatableTx) { var sigOpCount = 0; if (validatableTx.IsCoinbase) { return(0); } for (var inputIndex = 0; inputIndex < validatableTx.Transaction.Inputs.Length; inputIndex++) { sigOpCount += CountP2SHSigOps(validatableTx, inputIndex); } return(sigOpCount); }
private int CountP2SHSigOps(ValidatableTx validatableTx, int inputIndex) { if (validatableTx.IsCoinbase) { return(0); } var prevTxOutput = validatableTx.PrevTxOutputs[inputIndex]; if (prevTxOutput.IsPayToScriptHash()) { var script = validatableTx.Transaction.Inputs[inputIndex].ScriptSignature; ImmutableArray <byte>?data = null; // find the last item pushed onto the stack in the P2SH script signature... var index = 0; while (index < script.Length) { ScriptOp op; if (!GetOp(script, ref index, out op, out data)) { return(0); } else if (op > ScriptOp.OP_16) { return(0); } } if (data == null) { return(0); } // ...and count its sig ops return(CountLegacySigOps(data.Value, accurate: true)); } else { return(0); } }
public bool TryAddTransaction(DecodedTx decodedTx) { if (ContainsTransaction(decodedTx.Hash)) { // unconfirmed tx already exists return(false); } var tx = decodedTx.Transaction; UnconfirmedTx unconfirmedTx; // allow concurrent transaction adds if underlying storage supports it // in either case, lock waits for block add/rollback to finish if (storageManager.IsUnconfirmedTxesConcurrent) { updateLock.EnterReadLock(); } else { updateLock.EnterWriteLock(); } try { using (var chainState = coreDaemon.GetChainState()) { // verify each input is available to spend var prevTxOutputKeys = new HashSet <TxOutputKey>(); var prevTxOutputs = ImmutableArray.CreateBuilder <PrevTxOutput>(tx.Inputs.Length); var inputValue = 0UL; for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; if (!prevTxOutputKeys.Add(input.PrevTxOutputKey)) { // tx double spends one of its own inputs return(false); } UnspentTx unspentTx; if (!chainState.TryGetUnspentTx(input.PrevTxHash, out unspentTx)) { // input's prev output does not exist return(false); } if (input.PrevTxOutputIndex >= unspentTx.OutputStates.Length) { // input's prev output does not exist return(false); } if (unspentTx.OutputStates[(int)input.PrevTxOutputIndex] != OutputState.Unspent) { // input's prev output has already been spent return(false); } TxOutput txOutput; if (!chainState.TryGetUnspentTxOutput(input.PrevTxOutputKey, out txOutput)) { // input's prev output does not exist return(false); } var prevTxOutput = new PrevTxOutput(txOutput, unspentTx); prevTxOutputs.Add(prevTxOutput); checked { inputValue += prevTxOutput.Value; } } var outputValue = 0UL; for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++) { var output = tx.Outputs[outputIndex]; checked { outputValue += output.Value; } } if (outputValue > inputValue) { // transaction spends more than its inputs return(false); } // validation passed // create the unconfirmed tx var blockTx = new DecodedBlockTx(-1, decodedTx); var validatableTx = new ValidatableTx(blockTx, null, prevTxOutputs.ToImmutable()); unconfirmedTx = new UnconfirmedTx(validatableTx, DateTimeOffset.Now); // add the unconfirmed tx using (var handle = storageManager.OpenUnconfirmedTxesCursor()) { var unconfirmedTxesCursor = handle.Item; unconfirmedTxesCursor.BeginTransaction(); if (unconfirmedTxesCursor.TryAddTransaction(unconfirmedTx)) { unconfirmedTxesCursor.CommitTransaction(); } else { // unconfirmed tx already exists return(false); } } } } finally { if (storageManager.IsUnconfirmedTxesConcurrent) { updateLock.ExitReadLock(); } else { updateLock.ExitWriteLock(); } } UnconfirmedTxAdded?.Invoke(this, new UnconfirmedTxAddedEventArgs(unconfirmedTx)); return(true); }
public void ValidateTransaction(Chain newChain, ValidatableTx validatableTx) { // TODO any expensive validation can go here }
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 }