Exemple #1
0
 public void ValidateTransaction(Chain newChain, ValidatableTx validatableTx)
 {
     if (ValidateTransactionAction == null)
         coreRules.ValidateTransaction(newChain, validatableTx);
     else
         ValidateTransactionAction(newChain, validatableTx);
 }
Exemple #2
0
 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);
     }
 }
Exemple #3
0
 public UnconfirmedTx(ValidatableTx tx, DateTimeOffset dateSeen)
 {
     ValidatableTx = tx;
     DateSeen = dateSeen;
     Fee = tx.PrevTxOutputs.Sum(x => x.Value) - tx.Transaction.Outputs.Sum(x => x.Value);
 }
        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;
        }
Exemple #5
0
        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;
        }
Exemple #6
0
        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;
        }
Exemple #7
0
 public void ValidateTransaction(Chain newChain, ValidatableTx validatableTx)
 {
     // TODO any expensive validation can go here
 }
Exemple #8
0
        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
        }
Exemple #9
0
 public UnconfirmedTx(ValidatableTx tx, DateTimeOffset dateSeen)
 {
     ValidatableTx = tx;
     DateSeen      = dateSeen;
     Fee           = tx.PrevTxOutputs.Sum(x => x.Value) - tx.Transaction.Outputs.Sum(x => x.Value);
 }