2 * Novacoin classes library
3 * Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 using System.Collections.Generic;
23 using System.Diagnostics.Contracts;
24 using System.Numerics;
29 public class TransactionConstructorException : Exception
31 public TransactionConstructorException()
35 public TransactionConstructorException(string message)
40 public TransactionConstructorException(string message, Exception inner)
41 : base(message, inner)
47 /// Represents the transaction.
49 public class CTransaction
52 /// One cent = 10000 satoshis.
54 public const ulong nCent = 10000;
57 /// One coin = 1000000 satoshis.
59 public const ulong nCoin = 1000000;
61 /// Sanity checking threshold.
63 public const ulong nMaxMoney = 2000000000 * nCoin;
66 /// Maximum transaction size is 250Kb
68 public const uint nMaxTxSize = 250000;
70 public enum MinFeeMode
78 /// Version of transaction schema.
83 /// Transaction timestamp.
88 /// Array of transaction inputs
93 /// Array of transaction outputs
98 /// Block height or timestamp when transaction is final
100 public uint nLockTime;
103 /// Initialize an empty instance
105 public CTransaction()
107 // Initialize empty input and output arrays. Please note that such
108 // configuration is not valid for real transaction, you have to supply
109 // at least one input and one output.
113 vout = new CTxOut[0];
118 /// Initialize new instance as a copy of another transaction
120 /// <param name="tx">Transaction to copy from</param>
121 public CTransaction(CTransaction tx)
123 nVersion = tx.nVersion;
126 vin = new CTxIn[tx.vin.Length];
128 for (int i = 0; i < vin.Length; i++)
130 vin[i] = new CTxIn(tx.vin[i]);
133 vout = new CTxOut[tx.vout.Length];
135 for (int i = 0; i < vout.Length; i++)
137 vout[i] = new CTxOut(tx.vout[i]);
140 nLockTime = tx.nLockTime;
144 /// Attempts to execute all transaction scripts and validate the results.
146 /// <returns>Checking result.</returns>
147 public bool VerifyScripts()
154 TxOutItem txOutCursor = null;
155 for (int i = 0; i < vin.Length; i++)
157 var outpoint = vin[i].prevout;
159 if (!CBlockStore.Instance.GetTxOutCursor(outpoint, ref txOutCursor))
162 if (!ScriptCode.VerifyScript(vin[i].scriptSig, txOutCursor.scriptPubKey, this, i, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0))
170 /// Calculate amount of signature operations without trying to properly evaluate P2SH scripts.
172 public uint LegacySigOpCount
177 foreach (var txin in vin)
179 nSigOps += txin.scriptSig.GetSigOpCount(false);
181 foreach (var txout in vout)
183 nSigOps += txout.scriptPubKey.GetSigOpCount(false);
191 /// Basic sanity checkings
193 /// <returns>Checking result</returns>
194 public bool CheckTransaction()
196 if (Size > nMaxTxSize || vin.Length == 0 || vout.Length == 0)
201 // Check for empty or overflow output values
203 for (int i = 0; i < vout.Length; i++)
205 CTxOut txout = vout[i];
206 if (txout.IsEmpty && !IsCoinBase && !IsCoinStake)
208 // Empty outputs aren't allowed for user transactions.
212 nValueOut += txout.nValue;
213 if (!MoneyRange(nValueOut))
219 // Check for duplicate inputs
220 var InOutPoints = new List<COutPoint>();
221 foreach (var txin in vin)
223 if (InOutPoints.IndexOf(txin.prevout) != -1)
228 InOutPoints.Add(txin.prevout);
233 if (vin[0].scriptSig.Size < 2 || vin[0].scriptSig.Size > 100)
235 // Script size is invalid
241 foreach (var txin in vin)
243 if (txin.prevout.IsNull)
245 // Null input in non-coinbase transaction.
254 public bool IsFinal(uint nBlockHeight = 0, uint nBlockTime = 0)
256 // Time based nLockTime
261 if (nBlockHeight == 0)
263 nBlockHeight = uint.MaxValue; // TODO: stupid stub here, should be best height instead.
267 nBlockTime = NetInfo.GetAdjustedTime();
269 if (nLockTime < (nLockTime < NetInfo.nLockTimeThreshold ? nBlockHeight : nBlockTime))
273 foreach (var txin in vin)
284 /// Parse byte sequence and initialize new instance of CTransaction
286 /// <param name="txBytes">Byte sequence</param>
287 public CTransaction(byte[] txBytes)
291 var stream = new MemoryStream(txBytes);
292 var reader = new BinaryReader(stream);
294 nVersion = reader.ReadUInt32();
295 nTime = reader.ReadUInt32();
297 int nInputs = (int)VarInt.ReadVarInt(ref reader);
298 vin = new CTxIn[nInputs];
300 for (int nCurrentInput = 0; nCurrentInput < nInputs; nCurrentInput++)
303 vin[nCurrentInput] = new CTxIn();
305 vin[nCurrentInput].prevout = new COutPoint(reader.ReadBytes(36));
307 int nScriptSigLen = (int)VarInt.ReadVarInt(ref reader);
308 vin[nCurrentInput].scriptSig = new CScript(reader.ReadBytes(nScriptSigLen));
310 vin[nCurrentInput].nSequence = reader.ReadUInt32();
313 int nOutputs = (int)VarInt.ReadVarInt(ref reader);
314 vout = new CTxOut[nOutputs];
316 for (int nCurrentOutput = 0; nCurrentOutput < nOutputs; nCurrentOutput++)
318 // Fill outputs array
319 vout[nCurrentOutput] = new CTxOut();
320 vout[nCurrentOutput].nValue = reader.ReadUInt64();
322 int nScriptPKLen = (int)VarInt.ReadVarInt(ref reader);
323 vout[nCurrentOutput].scriptPubKey = new CScript(reader.ReadBytes(nScriptPKLen));
326 nLockTime = reader.ReadUInt32();
330 throw new TransactionConstructorException("Deserialization failed", e);
341 int nSize = 12; // nVersion, nTime, nLockLime
343 nSize += VarInt.GetEncodedSize(vin.Length);
344 nSize += VarInt.GetEncodedSize(vout.Length);
346 foreach (var input in vin)
351 foreach (var output in vout)
353 nSize += output.Size;
361 /// Read transactions array which is encoded in the block body.
363 /// <param name="wTxBytes">Bytes sequence</param>
364 /// <returns>Transactions array</returns>
365 internal static CTransaction[] ReadTransactionsList(ref BinaryReader reader)
369 // Read amount of transactions
370 int nTransactions = (int)VarInt.ReadVarInt(ref reader);
371 var tx = new CTransaction[nTransactions];
373 for (int nTx = 0; nTx < nTransactions; nTx++)
375 // Fill the transactions array
376 tx[nTx] = new CTransaction();
378 tx[nTx].nVersion = reader.ReadUInt32();
379 tx[nTx].nTime = reader.ReadUInt32();
382 tx[nTx].vin = CTxIn.ReadTxInList(ref reader);
385 tx[nTx].vout = CTxOut.ReadTxOutList(ref reader);
387 tx[nTx].nLockTime = reader.ReadUInt32();
395 throw new TransactionConstructorException("Deserialization failed", e);
399 public bool IsCoinBase
401 get { return (vin.Length == 1 && vin[0].prevout.IsNull && vout.Length >= 1); }
404 public bool IsCoinStake
408 return (vin.Length > 0 && (!vin[0].prevout.IsNull) && vout.Length >= 2 && vout[0].IsEmpty);
417 get { return CryptoUtils.ComputeHash256(this); }
421 /// Amount of novacoins spent by this transaction.
423 public ulong nValueOut
428 foreach (var txout in vout)
430 nValueOut += txout.nValue;
431 Contract.Assert(MoneyRange(txout.nValue) && MoneyRange(nValueOut));
438 /// A sequence of bytes, which corresponds to the current state of CTransaction.
440 public static implicit operator byte[] (CTransaction tx)
442 var stream = new MemoryStream();
443 var writer = new BinaryWriter(stream);
445 writer.Write(tx.nVersion);
446 writer.Write(tx.nTime);
447 writer.Write(VarInt.EncodeVarInt(tx.vin.LongLength));
449 foreach (var input in tx.vin)
454 writer.Write(VarInt.EncodeVarInt(tx.vout.LongLength));
456 foreach (var output in tx.vout)
458 writer.Write(output);
461 writer.Write(tx.nLockTime);
462 var resultBytes = stream.ToArray();
468 public override string ToString()
470 var sb = new StringBuilder();
472 sb.AppendFormat("CTransaction(\n nVersion={0},\n nTime={1},\n", nVersion, nTime);
474 foreach (var txin in vin)
476 sb.AppendFormat(" {0},\n", txin);
479 foreach (var txout in vout)
481 sb.AppendFormat(" {0},\n", txout);
484 sb.AppendFormat("\nnLockTime={0}\n)", nLockTime);
486 return sb.ToString();
489 public static bool MoneyRange(ulong nValue) { return (nValue <= nMaxMoney); }
492 /// Get total sigops.
494 /// <param name="inputs">Inputs map.</param>
495 /// <returns>Amount of sigops.</returns>
496 public uint GetP2SHSigOpCount(ref Dictionary<COutPoint, TxOutItem> inputs)
504 for (var i = 0; i < vin.Length; i++)
506 var prevout = GetOutputFor(vin[i], ref inputs);
507 if (prevout.scriptPubKey.IsPayToScriptHash)
509 nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig);
517 /// Get sum of inputs spent by this transaction.
519 /// <param name="inputs">Reference to innputs map.</param>
520 /// <returns>Sum of inputs.</returns>
521 public ulong GetValueIn(ref Dictionary<COutPoint, TxOutItem> inputs)
529 for (int i = 0; i < vin.Length; i++)
531 nResult += GetOutputFor(vin[i], ref inputs).nValue;
538 /// Helper method to find output in the map.
540 /// <param name="input">Transaction input.</param>
541 /// <param name="inputs">eference to inuts map.</param>
542 /// <returns>Parent output.</returns>
543 private CTxOut GetOutputFor(CTxIn input, ref Dictionary<COutPoint, TxOutItem> inputs)
545 if (!inputs.ContainsKey(input.prevout))
547 throw new Exception("No such input");
550 var outItem = inputs[input.prevout];
552 return new CTxOut(outItem.nValue, outItem.scriptPubKey);
555 internal bool GetCoinAge(ref Dictionary<COutPoint, TxOutItem> inputs, out ulong nCoinAge)
557 throw new NotImplementedException();
560 internal static ulong GetMinFee(int v1, bool v2, MinFeeMode gMF_BLOCK, int nTxSize)
562 throw new NotImplementedException();