/** * Novacoin classes library * Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com) * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ using System; using System.Text; using System.Collections.Generic; namespace Novacoin { [Serializable] public class TransactionConstructorException : Exception { public TransactionConstructorException() { } public TransactionConstructorException(string message) : base(message) { } public TransactionConstructorException(string message, Exception inner) : base(message, inner) { } } /// /// Represents the transaction. Any transaction must provide one input and one output at least. /// public class CTransaction { /// /// Version of transaction schema. /// public uint nVersion; /// /// Transaction timestamp. /// public uint nTime; /// /// Array of transaction inputs /// public CTxIn[] vin; /// /// Array of transaction outputs /// public CTxOut[] vout; /// /// Block height or timestamp when transaction is final /// public uint nLockTime; /// /// Initialize an empty instance /// public CTransaction() { // Initialize empty input and output arrays. Please note that such // configuration is not valid for real transaction, you have to supply // at least one input and one output. nVersion = 1; nTime = 0; vin = new CTxIn[0]; vout = new CTxOut[0]; nLockTime = 0; } /// /// Initialize new instance as a copy of another transaction /// /// Transaction to copy from public CTransaction(CTransaction tx) { nVersion = tx.nVersion; nTime = tx.nTime; vin = new CTxIn[tx.vin.Length]; for (int i = 0; i < vin.Length; i++) { vin[i] = new CTxIn(tx.vin[i]); } vout = new CTxOut[tx.vout.Length]; for (int i = 0; i < vout.Length; i++) { vout[i] = new CTxOut(tx.vout[i]); } nLockTime = tx.nLockTime; } public bool VerifyScripts() { if (IsCoinBase) { return true; } CTransaction txPrev = null; for (int i = 0; i < vin.Length; i++) { var outpoint = vin[i].prevout; if (!CBlockStore.Instance.GetTransaction(outpoint.hash, ref txPrev)) return false; if (!ScriptCode.VerifyScript(vin[i].scriptSig, txPrev.vout[outpoint.n].scriptPubKey, this, i, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0)) return false; } return true; } /// /// Parse byte sequence and initialize new instance of CTransaction /// /// Byte sequence public CTransaction(byte[] txBytes) { try { var wBytes = new ByteQueue(txBytes); nVersion = BitConverter.ToUInt32(wBytes.Get(4), 0); nTime = BitConverter.ToUInt32(wBytes.Get(4), 0); int nInputs = (int)wBytes.GetVarInt(); vin = new CTxIn[nInputs]; for (int nCurrentInput = 0; nCurrentInput < nInputs; nCurrentInput++) { // Fill inputs array vin[nCurrentInput] = new CTxIn(); vin[nCurrentInput].prevout = new COutPoint(wBytes.Get(36)); int nScriptSigLen = (int)wBytes.GetVarInt(); vin[nCurrentInput].scriptSig = new CScript(wBytes.Get(nScriptSigLen)); vin[nCurrentInput].nSequence = BitConverter.ToUInt32(wBytes.Get(4), 0); } int nOutputs = (int)wBytes.GetVarInt(); vout = new CTxOut[nOutputs]; for (int nCurrentOutput = 0; nCurrentOutput < nOutputs; nCurrentOutput++) { // Fill outputs array vout[nCurrentOutput] = new CTxOut(); vout[nCurrentOutput].nValue = BitConverter.ToUInt64(wBytes.Get(8), 0); int nScriptPKLen = (int)wBytes.GetVarInt(); vout[nCurrentOutput].scriptPubKey = new CScript(wBytes.Get(nScriptPKLen)); } nLockTime = BitConverter.ToUInt32(wBytes.Get(4), 0); } catch (Exception e) { throw new TransactionConstructorException("Deserialization failed", e); } } /// /// Serialized size /// public int Size { get { int nSize = 12; // nVersion, nTime, nLockLime nSize += VarInt.GetEncodedSize(vin.Length); nSize += VarInt.GetEncodedSize(vout.Length); foreach (var input in vin) { nSize += input.Size; } foreach (var output in vout) { nSize += output.Size; } return nSize; } } /// /// Read transactions array which is encoded in the block body. /// /// Bytes sequence /// Transactions array public static CTransaction[] ReadTransactionsList(ref ByteQueue wTxBytes) { try { // Read amount of transactions int nTransactions = (int)wTxBytes.GetVarInt(); var tx = new CTransaction[nTransactions]; for (int nTx = 0; nTx < nTransactions; nTx++) { // Fill the transactions array tx[nTx] = new CTransaction(); tx[nTx].nVersion = BitConverter.ToUInt32(wTxBytes.Get(4), 0); tx[nTx].nTime = BitConverter.ToUInt32(wTxBytes.Get(4), 0); // Inputs array tx[nTx].vin = CTxIn.ReadTxInList(ref wTxBytes); // outputs array tx[nTx].vout = CTxOut.ReadTxOutList(ref wTxBytes); tx[nTx].nLockTime = BitConverter.ToUInt32(wTxBytes.Get(4), 0); } return tx; } catch (Exception e) { throw new TransactionConstructorException("Deserialization failed", e); } } public bool IsCoinBase { get { return (vin.Length == 1 && vin[0].prevout.IsNull && vout.Length >= 1); } } public bool IsCoinStake { get { return (vin.Length > 0 && (!vin[0].prevout.IsNull) && vout.Length >= 2 && vout[0].IsEmpty); } } /// /// Transaction hash /// public Hash256 Hash { get { return Hash256.Compute256(this); } } /// /// A sequence of bytes, which corresponds to the current state of CTransaction. /// public static implicit operator byte[] (CTransaction tx) { var resultBytes = new List(); resultBytes.AddRange(BitConverter.GetBytes(tx.nVersion)); resultBytes.AddRange(BitConverter.GetBytes(tx.nTime)); resultBytes.AddRange(VarInt.EncodeVarInt(tx.vin.LongLength)); foreach (var input in tx.vin) { resultBytes.AddRange((byte[])input); } resultBytes.AddRange(VarInt.EncodeVarInt(tx.vout.LongLength)); foreach (var output in tx.vout) { resultBytes.AddRange((byte[])output); } resultBytes.AddRange(BitConverter.GetBytes(tx.nLockTime)); return resultBytes.ToArray(); } public override string ToString() { var sb = new StringBuilder(); sb.AppendFormat("CTransaction(\n nVersion={0},\n nTime={1},\n", nVersion, nTime); foreach (var txin in vin) { sb.AppendFormat(" {0},\n", txin.ToString()); } foreach (var txout in vout) { sb.AppendFormat(" {0},\n", txout.ToString()); } sb.AppendFormat("\nnLockTime={0}\n)", nLockTime); return sb.ToString(); } } }