Esempio n. 1
0
 /// <summary>
 /// Used only in creation of the genesis block.
 /// </summary>
 internal TransactionInput(NetworkParameters networkParams, Transaction parentTransaction, byte[] scriptBytes)
     : base(networkParams)
 {
     ScriptBytes = scriptBytes;
     Outpoint = new TransactionOutPoint(networkParams, -1, null);
     _sequence = uint.MaxValue;
     ParentTransaction = parentTransaction;
 }
Esempio n. 2
0
 /// <summary>
 /// Used only in creation of the genesis blocks and in unit tests.
 /// </summary>
 internal TransactionOutput(NetworkParameters networkParams, Transaction parent, byte[] scriptBytes)
     : base(networkParams)
 {
     _scriptBytes = scriptBytes;
     _value = Utils.ToNanoCoins(50, 0);
     ParentTransaction = parent;
     _availableForSpending = true;
 }
Esempio n. 3
0
 internal TransactionOutput(NetworkParameters networkParams, Transaction parent, ulong value, Address to)
     : base(networkParams)
 {
     _value = value;
     _scriptBytes = Script.CreateOutputScript(to);
     ParentTransaction = parent;
     _availableForSpending = true;
 }
Esempio n. 4
0
 /// <summary>
 /// Creates an UNSIGNED input that links to the given output
 /// </summary>
 internal TransactionInput(NetworkParameters networkParams, Transaction parentTransaction, TransactionOutput output)
     : base(networkParams)
 {
     var outputIndex = output.Index;
     Outpoint = new TransactionOutPoint(networkParams, outputIndex, output.ParentTransaction);
     ScriptBytes = EmptyArray;
     _sequence = uint.MaxValue;
     ParentTransaction = parentTransaction;
 }
Esempio n. 5
0
 internal TransactionOutPoint(NetworkParameters networkParams, int index, Transaction fromTx)
     : base(networkParams)
 {
     Index = index;
     if (fromTx != null)
     {
         Hash = fromTx.Hash;
         FromTx = fromTx;
     }
     else
     {
         // This happens when constructing the genesis block.
         Hash = Sha256Hash.ZeroHash;
     }
 }
Esempio n. 6
0
 private void ProcessTransaction(Transaction tx)
 {
     Log.DebugFormat("Received broadcast tx {0}", tx.HashAsString);
 }
Esempio n. 7
0
        /// <exception cref="VerificationException"/>
        /// <exception cref="ScriptException"/>
        private void Receive(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType, bool reorg)
        {
            lock (this)
            {
                // Runs in a peer thread.
                var prevBalance = GetBalance();

                var txHash = tx.Hash;

                var bestChain = blockType == BlockChain.NewBlockType.BestChain;
                var sideChain = blockType == BlockChain.NewBlockType.SideChain;

                var valueSentFromMe = tx.GetValueSentFromMe(this);
                var valueSentToMe = tx.GetValueSentToMe(this);
                var valueDifference = (long) (valueSentToMe - valueSentFromMe);

                if (!reorg)
                {
                    Log.InfoFormat("Received tx{0} for {1} BTC: {2}", sideChain ? " on a side chain" : "",
                                    Utils.BitcoinValueToFriendlystring(valueDifference), tx.HashAsString);
                }

                // If this transaction is already in the wallet we may need to move it into a different pool. At the very
                // least we need to ensure we're manipulating the canonical object rather than a duplicate.
                Transaction wtx;
                if (Pending.TryGetValue(txHash, out wtx))
                {
                    Pending.Remove(txHash);
                    Log.Info("  <-pending");
                    // A transaction we created appeared in a block. Probably this is a spend we broadcast that has been
                    // accepted by the network.
                    //
                    // Mark the tx as appearing in this block so we can find it later after a re-org.
                    wtx.AddBlockAppearance(block);
                    if (bestChain)
                    {
                        if (valueSentToMe.Equals(0))
                        {
                            // There were no change transactions so this tx is fully spent.
                            Log.Info("  ->spent");
                            Debug.Assert(!Spent.ContainsKey(wtx.Hash), "TX in both pending and spent pools");
                            Spent[wtx.Hash] = wtx;
                        }
                        else
                        {
                            // There was change back to us, or this tx was purely a spend back to ourselves (perhaps for
                            // anonymization purposes).
                            Log.Info("  ->unspent");
                            Debug.Assert(!Unspent.ContainsKey(wtx.Hash), "TX in both pending and unspent pools");
                            Unspent[wtx.Hash] = wtx;
                        }
                    }
                    else if (sideChain)
                    {
                        // The transaction was accepted on an inactive side chain, but not yet by the best chain.
                        Log.Info("  ->inactive");
                        // It's OK for this to already be in the inactive pool because there can be multiple independent side
                        // chains in which it appears:
                        //
                        //     b1 --> b2
                        //        \-> b3
                        //        \-> b4 (at this point it's already present in 'inactive'
                        if (_inactive.ContainsKey(wtx.Hash))
                            Log.Info("Saw a transaction be incorporated into multiple independent side chains");
                        _inactive[wtx.Hash] = wtx;
                        // Put it back into the pending pool, because 'pending' means 'waiting to be included in best chain'.
                        Pending[wtx.Hash] = wtx;
                    }
                }
                else
                {
                    if (!reorg)
                    {
                        // Mark the tx as appearing in this block so we can find it later after a re-org.
                        tx.AddBlockAppearance(block);
                    }
                    // This TX didn't originate with us. It could be sending us coins and also spending our own coins if keys
                    // are being shared between different wallets.
                    if (sideChain)
                    {
                        Log.Info("  ->inactive");
                        _inactive[tx.Hash] = tx;
                    }
                    else if (bestChain)
                    {
                        ProcessTxFromBestChain(tx);
                    }
                }

                Log.InfoFormat("Balance is now: {0}", Utils.BitcoinValueToFriendlystring(GetBalance()));

                // Inform anyone interested that we have new coins. Note: we may be re-entered by the event listener,
                // so we must not make assumptions about our state after this loop returns! For example,
                // the balance we just received might already be spent!
                if (!reorg && bestChain && valueDifference > 0 && CoinsReceived != null)
                {
                    lock (CoinsReceived)
                    {
                        CoinsReceived(this, new WalletCoinsReceivedEventArgs(tx, prevBalance, GetBalance()));
                    }
                }
            }
        }
Esempio n. 8
0
 // A GetDataFuture wraps the result of a getblock or (in future) getTransaction so the owner of the object can
 // decide whether to wait forever, wait for a short while or check later after doing other work.
 /// <summary>
 /// Send the given Transaction, ie, make a payment with BitCoins. To create a transaction you can broadcast, use
 /// a <see cref="Wallet"/>. After the broadcast completes, confirm the send using the wallet confirmSend() method.
 /// </summary>
 /// <exception cref="IOException"/>
 internal void BroadcastTransaction(Transaction tx)
 {
     _conn.WriteMessage(tx);
 }
Esempio n. 9
0
 /// <summary>
 /// If the transactions outputs are all marked as spent, and it's in the unspent map, move it.
 /// </summary>
 private void MaybeMoveTxToSpent(Transaction tx, string context)
 {
     if (tx.IsEveryOutputSpent())
     {
         // There's nothing left I can spend in this transaction.
         if (Unspent.Remove(tx.Hash))
         {
             if (Log.IsInfoEnabled)
             {
                 Log.Info("  " + context + " <-unspent");
                 Log.Info("  " + context + " ->spent");
             }
             Spent[tx.Hash] = tx;
         }
     }
 }
Esempio n. 10
0
 /// <summary>
 /// Handle when a transaction becomes newly active on the best chain, either due to receiving a new block or a
 /// re-org making inactive transactions active.
 /// </summary>
 /// <exception cref="VerificationException"/>
 private void ProcessTxFromBestChain(Transaction tx)
 {
     // This TX may spend our existing outputs even though it was not pending. This can happen in unit
     // tests and if keys are moved between wallets.
     UpdateForSpends(tx);
     if (!tx.GetValueSentToMe(this).Equals(0))
     {
         // It's sending us coins.
         Log.Info("  new tx ->unspent");
         Debug.Assert(!Unspent.ContainsKey(tx.Hash), "TX was received twice");
         Unspent[tx.Hash] = tx;
     }
     else
     {
         // It spent some of our coins and did not send us any.
         Log.Info("  new tx ->spent");
         Debug.Assert(!Spent.ContainsKey(tx.Hash), "TX was received twice");
         Spent[tx.Hash] = tx;
     }
 }
Esempio n. 11
0
 /// <summary>
 /// Adds a transaction to this block.
 /// </summary>
 internal void AddTransaction(Transaction t)
 {
     if (Transactions == null)
     {
         Transactions = new List<Transaction>();
     }
     Transactions.Add(t);
     // Force a recalculation next time the values are needed.
     _merkleRoot = null;
     _hash = null;
 }
Esempio n. 12
0
 /// <summary>
 /// Called by the <see cref="BlockChain"/> when we receive a new block that sends coins to one of our addresses or
 /// spends coins from one of our addresses (note that a single transaction can do both).
 /// </summary>
 /// <remarks>
 /// This is necessary for the internal book-keeping Wallet does. When a transaction is received that sends us
 /// coins it is added to a pool so we can use it later to create spends. When a transaction is received that
 /// consumes outputs they are marked as spent so they won't be used in future.<p/>
 /// A transaction that spends our own coins can be received either because a spend we created was accepted by the
 /// network and thus made it into a block, or because our keys are being shared between multiple instances and
 /// some other node spent the coins instead. We still have to know about that to avoid accidentally trying to
 /// double spend.<p/>
 /// A transaction may be received multiple times if is included into blocks in parallel chains. The blockType
 /// parameter describes whether the containing block is on the main/best chain or whether it's on a presently
 /// inactive side chain. We must still record these transactions and the blocks they appear in because a future
 /// block might change which chain is best causing a reorganize. A re-org can totally change our balance!
 /// </remarks>
 /// <exception cref="VerificationException"/>
 /// <exception cref="ScriptException"/>
 internal void Receive(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType)
 {
     lock (this)
     {
         Receive(tx, block, blockType, false);
     }
 }
Esempio n. 13
0
 public TransactionConfidence(Transaction tx)
 {
     // Assume a default number of peers for our set.
     broadcastBy = new HashSet<PeerAddress>();
     transaction = tx;
 }
Esempio n. 14
0
 /// <summary>
 /// Broadcast a transaction to all connected peers.
 /// </summary>
 /// <returns>Whether we sent to at least one peer.</returns>
 public bool BroadcastTransaction(Transaction tx)
 {
     var success = false;
     lock (_peers)
     {
         foreach (var peer in _peers)
         {
             try
             {
                 peer.BroadcastTransaction(tx);
                 success = true;
             }
             catch (IOException e)
             {
                 Log.Error("failed to broadcast to " + peer, e);
             }
         }
     }
     return success;
 }
Esempio n. 15
0
 /// <summary>
 /// Updates the wallet by checking if this TX spends any of our outputs. This is not used normally because
 /// when we receive our own spends, we've already marked the outputs as spent previously (during tx creation) so
 /// there's no need to go through and do it again.
 /// </summary>
 /// <exception cref="VerificationException"/>
 private void UpdateForSpends(Transaction tx)
 {
     // tx is on the best chain by this point.
     foreach (var input in tx.Inputs)
     {
         var result = input.Connect(Unspent, false);
         if (result == TransactionInput.ConnectionResult.NoSuchTx)
         {
             // Not found in the unspent map. Try again with the spent map.
             result = input.Connect(Spent, false);
             if (result == TransactionInput.ConnectionResult.NoSuchTx)
             {
                 // Doesn't spend any of our outputs or is coinbase.
                 continue;
             }
         }
         if (result == TransactionInput.ConnectionResult.AlreadySpent)
         {
             // Double spend! This must have overridden a pending tx, or the block is bad (contains transactions
             // that illegally double spend: should never occur if we are connected to an honest node).
             //
             // Work backwards like so:
             //
             //   A  -> spent by B [pending]
             //     \-> spent by C [chain]
             var doubleSpent = input.Outpoint.FromTx; // == A
             Debug.Assert(doubleSpent != null);
             var index = input.Outpoint.Index;
             var output = doubleSpent.Outputs[index];
             var spentBy = output.SpentBy;
             Debug.Assert(spentBy != null);
             var connected = spentBy.ParentTransaction;
             Debug.Assert(connected != null);
             if (Pending.Remove(connected.Hash))
             {
                 Log.InfoFormat("Saw double spend from chain override pending tx {0}", connected.HashAsString);
                 Log.Info("  <-pending ->dead");
                 _dead[connected.Hash] = connected;
                 // Now forcibly change the connection.
                 input.Connect(Unspent, true);
                 // Inform the event listeners of the newly dead tx.
                 if (DeadTransaction != null)
                 {
                     lock (DeadTransaction)
                     {
                         DeadTransaction(this, new WalletDeadTransactionEventArgs(connected, tx));
                     }
                 }
             }
         }
         else if (result == TransactionInput.ConnectionResult.Success)
         {
             // Otherwise we saw a transaction spend our coins, but we didn't try and spend them ourselves yet.
             // The outputs are already marked as spent by the connect call above, so check if there are any more for
             // us to use. Move if not.
             var connected = input.Outpoint.FromTx;
             MaybeMoveTxToSpent(connected, "prevtx");
         }
     }
 }
Esempio n. 16
0
 private static Block CreateGenesis(NetworkParameters n)
 {
     var genesisBlock = new Block(n);
     var t = new Transaction(n);
     // A script containing the difficulty bits and the following message:
     //
     //   "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
     var bytes = Hex.Decode("04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73");
     t.AddInput(new TransactionInput(n, t, bytes));
     using (var scriptPubKeyBytes = new MemoryStream())
     {
         Script.WriteBytes(scriptPubKeyBytes, Hex.Decode("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"));
         scriptPubKeyBytes.WriteByte((byte) OpCode.OP_CHECKSIG);
         t.AddOutput(new TransactionOutput(n, t, scriptPubKeyBytes.ToArray()));
     }
     genesisBlock.AddTransaction(t);
     return genesisBlock;
 }
Esempio n. 17
0
        private void ReprocessTxAfterReorg(IDictionary<Sha256Hash, Transaction> pool, Transaction tx)
        {
            Log.InfoFormat("  TX {0}", tx.HashAsString);
            var numInputs = tx.Inputs.Count;
            var noSuchTx = 0;
            var success = 0;
            var isDead = false;
            foreach (var input in tx.Inputs)
            {
                if (input.IsCoinBase)
                {
                    // Input is not in our wallet so there is "no such input tx", bit of an abuse.
                    noSuchTx++;
                    continue;
                }
                var result = input.Connect(pool, false);
                if (result == TransactionInput.ConnectionResult.Success)
                {
                    success++;
                }
                else if (result == TransactionInput.ConnectionResult.NoSuchTx)
                {
                    noSuchTx++;
                }
                else if (result == TransactionInput.ConnectionResult.AlreadySpent)
                {
                    isDead = true;
                    // This transaction was replaced by a double spend on the new chain. Did you just reverse
                    // your own transaction? I hope not!!
                    Log.Info("   ->dead, will not confirm now unless there's another re-org");
                    var doubleSpent = input.GetConnectedOutput(pool);
                    var replacement = doubleSpent.SpentBy.ParentTransaction;
                    _dead[tx.Hash] = tx;
                    Pending.Remove(tx.Hash);
                    // Inform the event listeners of the newly dead tx.
                    if (DeadTransaction != null)
                    {
                        lock (DeadTransaction)
                        {
                            DeadTransaction(this, new WalletDeadTransactionEventArgs(tx, replacement));
                        }
                    }
                    break;
                }
            }
            if (isDead) return;

            if (noSuchTx == numInputs)
            {
                Log.Info("   ->inactive");
                _inactive[tx.Hash] = tx;
            }
            else if (success == numInputs - noSuchTx)
            {
                // All inputs are either valid for spending or don't come from us. Miners are trying to re-include it.
                Log.Info("   ->pending");
                Pending[tx.Hash] = tx;
                _dead.Remove(tx.Hash);
            }
        }
Esempio n. 18
0
        /// <exception cref="ProtocolException"/>
        protected override void Parse()
        {
            _version = ReadUint32();
            _prevBlockHash = ReadHash();
            _merkleRoot = ReadHash();
            _time = ReadUint32();
            _difficultyTarget = ReadUint32();
            _nonce = ReadUint32();

            _hash = new Sha256Hash(Utils.ReverseBytes(Utils.DoubleDigest(Bytes, 0, Cursor)));

            if (Cursor == Bytes.Length)
            {
                // This message is just a header, it has no transactions.
                return;
            }

            var numTransactions = (int) ReadVarInt();
            Transactions = new List<Transaction>(numTransactions);
            for (var i = 0; i < numTransactions; i++)
            {
                var tx = new Transaction(Params, Bytes, Cursor);
                Transactions.Add(tx);
                Cursor += tx.MessageSize;
            }
        }
Esempio n. 19
0
        /// <summary>
        /// Returns a solved block that builds on top of this one. This exists for unit tests.
        /// </summary>
        internal Block CreateNextBlock(Address to, uint time)
        {
            var b = new Block(Params);
            b.DifficultyTarget = _difficultyTarget;
            b.AddCoinbaseTransaction(_emptyBytes);

            // Add a transaction paying 50 coins to the "to" address.
            var t = new Transaction(Params);
            t.AddOutput(new TransactionOutput(Params, t, Utils.ToNanoCoins(50, 0), to));
            // The input does not really need to be a valid signature, as long as it has the right general form.
            var input = new TransactionInput(Params, t, Script.CreateInputScript(_emptyBytes, _emptyBytes));
            // Importantly the outpoint hash cannot be zero as that's how we detect a coinbase transaction in isolation
            // but it must be unique to avoid 'different' transactions looking the same.
            var counter = new byte[32];
            counter[0] = (byte) _txCounter++;
            input.Outpoint.Hash = new Sha256Hash(counter);
            t.AddInput(input);
            b.AddTransaction(t);

            b.PrevBlockHash = Hash;
            b.TimeSeconds = time;
            b.Solve();
            b.VerifyHeader();
            return b;
        }
Esempio n. 20
0
 /// <summary>
 /// Deserializes a transaction output message. This is usually part of a transaction message.
 /// </summary>
 /// <exception cref="ProtocolException"/>
 public TransactionOutput(NetworkParameters networkParams, Transaction parent, byte[] payload, int offset)
     : base(networkParams, payload, offset)
 {
     ParentTransaction = parent;
     _availableForSpending = true;
 }
Esempio n. 21
0
        /// <summary>
        /// Creates a transaction that sends $coins.$cents BTC to the given address.
        /// </summary>
        /// <remarks>
        /// IMPORTANT: This method does NOT update the wallet. If you call createSend again you may get two transactions
        /// that spend the same coins. You have to call confirmSend on the created transaction to prevent this,
        /// but that should only occur once the transaction has been accepted by the network. This implies you cannot have
        /// more than one outstanding sending tx at once.
        /// </remarks>
        /// <param name="address">The BitCoin address to send the money to.</param>
        /// <param name="nanocoins">How much currency to send, in nanocoins.</param>
        /// <param name="changeAddress">
        /// Which address to send the change to, in case we can't make exactly the right value from
        /// our coins. This should be an address we own (is in the keychain).
        /// </param>
        /// <returns>
        /// A new <see cref="Transaction"/> or null if we cannot afford this send.
        /// </returns>
        internal Transaction CreateSend(Address address, ulong nanocoins, Address changeAddress)
        {
            lock (this)
            {
                Log.Info("Creating send tx to " + address + " for " +
                          Utils.BitcoinValueToFriendlystring(nanocoins));
                // To send money to somebody else, we need to do gather up transactions with unspent outputs until we have
                // sufficient value. Many coin selection algorithms are possible, we use a simple but suboptimal one.
                // TODO: Sort coins so we use the smallest first, to combat wallet fragmentation and reduce fees.
                var valueGathered = 0UL;
                var gathered = new LinkedList<TransactionOutput>();
                foreach (var tx in Unspent.Values)
                {
                    foreach (var output in tx.Outputs)
                    {
                        if (!output.IsAvailableForSpending) continue;
                        if (!output.IsMine(this)) continue;
                        gathered.AddLast(output);
                        valueGathered += output.Value;
                    }
                    if (valueGathered >= nanocoins) break;
                }
                // Can we afford this?
                if (valueGathered < nanocoins)
                {
                    Log.Info("Insufficient value in wallet for send, missing " +
                              Utils.BitcoinValueToFriendlystring(nanocoins - valueGathered));
                    // TODO: Should throw an exception here.
                    return null;
                }
                Debug.Assert(gathered.Count > 0);
                var sendTx = new Transaction(_params);
                sendTx.AddOutput(new TransactionOutput(_params, sendTx, nanocoins, address));
                var change = (long) (valueGathered - nanocoins);
                if (change > 0)
                {
                    // The value of the inputs is greater than what we want to send. Just like in real life then,
                    // we need to take back some coins ... this is called "change". Add another output that sends the change
                    // back to us.
                    Log.Info("  with " + Utils.BitcoinValueToFriendlystring((ulong) change) + " coins change");
                    sendTx.AddOutput(new TransactionOutput(_params, sendTx, (ulong) change, changeAddress));
                }
                foreach (var output in gathered)
                {
                    sendTx.AddInput(output);
                }

                // Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
                sendTx.SignInputs(Transaction.SigHash.All, this);
                Log.InfoFormat("  created {0}", sendTx.HashAsString);
                return sendTx;
            }
        }
Esempio n. 22
0
 /// <param name="tx">The transaction which sent us the coins.</param>
 /// <param name="prevBalance">Balance before the coins were received.</param>
 /// <param name="newBalance">Current balance of the wallet.</param>
 public WalletCoinsReceivedEventArgs(Transaction tx, ulong prevBalance, ulong newBalance)
 {
     Tx = tx;
     PrevBalance = prevBalance;
     NewBalance = newBalance;
 }
Esempio n. 23
0
 /// <summary>
 /// Adds a coinbase transaction to the block. This exists for unit tests.
 /// </summary>
 internal void AddCoinbaseTransaction(byte[] pubKeyTo)
 {
     Transactions = new List<Transaction>();
     var coinbase = new Transaction(Params);
     // A real coinbase transaction has some stuff in the scriptSig like the extraNonce and difficulty. The
     // transactions are distinguished by every TX output going to a different key.
     //
     // Here we will do things a bit differently so a new address isn't needed every time. We'll put a simple
     // counter in the scriptSig so every transaction has a different hash.
     coinbase.AddInput(new TransactionInput(Params, coinbase, new[] {(byte) _txCounter++}));
     coinbase.AddOutput(new TransactionOutput(Params, coinbase, Script.CreateOutputScript(pubKeyTo)));
     Transactions.Add(coinbase);
 }
Esempio n. 24
0
 public ConfidenceChangedEventArgs(Transaction transaction)
 {
     Transaction = transaction;
 }
Esempio n. 25
0
 /// <param name="deadTx">The transaction that is newly dead.</param>
 /// <param name="replacementTx">The transaction that killed it.</param>
 public WalletDeadTransactionEventArgs(Transaction deadTx, Transaction replacementTx)
 {
     DeadTx = deadTx;
     ReplacementTx = replacementTx;
 }
Esempio n. 26
0
 /// <summary>
 /// Call this when we have successfully transmitted the send tx to the network, to update the wallet.
 /// </summary>
 internal void ConfirmSend(Transaction tx)
 {
     lock (this)
     {
         Debug.Assert(!Pending.ContainsKey(tx.Hash), "confirmSend called on the same transaction twice");
         Log.InfoFormat("confirmSend of {0}", tx.HashAsString);
         // Mark the outputs of the used transactions as spent, so we don't try and spend it again.
         foreach (var input in tx.Inputs)
         {
             var connectedOutput = input.Outpoint.ConnectedOutput;
             var connectedTx = connectedOutput.ParentTransaction;
             connectedOutput.MarkAsSpent(input);
             MaybeMoveTxToSpent(connectedTx, "spent tx");
         }
         // Add to the pending pool. It'll be moved out once we receive this transaction on the best chain.
         Pending[tx.Hash] = tx;
     }
 }
Esempio n. 27
0
 /// <summary>
 /// Deserializes an input message. This is usually part of a transaction message.
 /// </summary>
 /// <exception cref="ProtocolException"/>
 public TransactionInput(NetworkParameters networkParams, Transaction parentTransaction, byte[] payload, int offset)
     : base(networkParams, payload, offset)
 {
     ParentTransaction = parentTransaction;
 }