Replace CTransaction constructor with MemoryStream based implementation.
[NovacoinLibrary.git] / Novacoin / CTransaction.cs
1 \feff/**
2  *  Novacoin classes library
3  *  Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
4
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.
9
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.
14
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/>.
17  */
18
19 using System;
20 using System.Text;
21 using System.Collections.Generic;
22 using System.IO;
23
24 namespace Novacoin
25 {
26     [Serializable]
27     public class TransactionConstructorException : Exception
28     {
29         public TransactionConstructorException()
30         {
31         }
32
33         public TransactionConstructorException(string message)
34                 : base(message)
35         {
36         }
37
38         public TransactionConstructorException(string message, Exception inner)
39                 : base(message, inner)
40         {
41         }
42     }
43
44     /// <summary>
45     /// Represents the transaction. Any transaction must provide one input and one output at least.
46     /// </summary>
47     public class CTransaction
48     {
49         /// <summary>
50         /// One coin = 1000000 satoshis.
51         /// </summary>
52         public const ulong nCoin = 1000000;
53         /// <summary>
54         /// Sanity checking threshold.
55         /// </summary>
56         public const ulong nMaxMoney = 2000000000 * nCoin;
57
58         /// <summary>
59         /// Version of transaction schema.
60         /// </summary>
61         public uint nVersion;
62
63         /// <summary>
64         /// Transaction timestamp.
65         /// </summary>
66         public uint nTime;
67
68         /// <summary>
69         /// Array of transaction inputs
70         /// </summary>
71         public CTxIn[] vin;
72
73         /// <summary>
74         /// Array of transaction outputs
75         /// </summary>
76         public CTxOut[] vout;
77
78         /// <summary>
79         /// Block height or timestamp when transaction is final
80         /// </summary>
81         public uint nLockTime;
82
83         /// <summary>
84         /// Initialize an empty instance
85         /// </summary>
86         public CTransaction()
87         {
88             // Initialize empty input and output arrays. Please note that such 
89             // configuration is not valid for real transaction, you have to supply 
90             // at least one input and one output.
91             nVersion = 1;
92             nTime = 0;
93             vin = new CTxIn[0];
94             vout = new CTxOut[0];
95             nLockTime = 0;
96         }
97
98         /// <summary>
99         /// Initialize new instance as a copy of another transaction
100         /// </summary>
101         /// <param name="tx">Transaction to copy from</param>
102         public CTransaction(CTransaction tx)
103         {
104             nVersion = tx.nVersion;
105             nTime = tx.nTime;
106
107             vin = new CTxIn[tx.vin.Length];
108
109             for (int i = 0; i < vin.Length; i++)
110             {
111                 vin[i] = new CTxIn(tx.vin[i]);
112             }
113
114             vout = new CTxOut[tx.vout.Length];
115
116             for (int i = 0; i < vout.Length; i++)
117             {
118                 vout[i] = new CTxOut(tx.vout[i]);
119             }
120
121             nLockTime = tx.nLockTime;
122         }
123
124         /// <summary>
125         /// Attempts to execute all transaction scripts and validate the results.
126         /// </summary>
127         /// <returns>Checking result.</returns>
128         public bool VerifyScripts()
129         {
130             if (IsCoinBase)
131             {
132                 return true;
133             }
134
135             CTransaction txPrev = null;
136             for (int i = 0; i < vin.Length; i++)
137             {
138                 var outpoint = vin[i].prevout;
139
140                 if (!CBlockStore.Instance.GetTransaction(outpoint.hash, ref txPrev))
141                     return false;
142
143                 if (!ScriptCode.VerifyScript(vin[i].scriptSig, txPrev.vout[outpoint.n].scriptPubKey, this, i, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0))
144                     return false;
145             }
146
147             return true;
148         }
149
150         /// <summary>
151         /// Calculate amount of signature operations without trying to properly evaluate P2SH scripts.
152         /// </summary>
153         public uint LegacySigOpCount
154         {
155             get
156             {
157                 uint nSigOps = 0;
158                 foreach (var txin in vin)
159                 {
160                     nSigOps += txin.scriptSig.GetSigOpCount(false);
161                 }
162                 foreach (var txout in vout)
163                 {
164                     nSigOps += txout.scriptPubKey.GetSigOpCount(false);
165                 }
166
167                 return nSigOps;
168             }
169         }
170
171         /// <summary>
172         /// Basic sanity checkings
173         /// </summary>
174         /// <returns>Checking result</returns>
175         public bool CheckTransaction()
176         {
177             if (Size > 250000 || vin.Length == 0 || vout.Length == 0)
178             {
179                 return false;
180             }
181
182             // Check for empty or overflow output values
183             ulong nValueOut = 0;
184             for (int i = 0; i < vout.Length; i++)
185             {
186                 CTxOut txout = vout[i];
187                 if (txout.IsEmpty && !IsCoinBase && !IsCoinStake)
188                 {
189                     // Empty outputs aren't allowed for user transactions.
190                     return false;
191                 }
192
193                 nValueOut += txout.nValue;
194                 if (!MoneyRange(nValueOut))
195                 {
196                     return false;
197                 }
198             }
199
200             // Check for duplicate inputs
201             var InOutPoints = new List<COutPoint>();
202             foreach (var txin in vin)
203             {
204                 if (InOutPoints.IndexOf(txin.prevout) != -1)
205                 {
206                     // Duplicate input.
207                     return false;
208                 }
209                 InOutPoints.Add(txin.prevout);
210             }
211
212             if (IsCoinBase)
213             {
214                 if (vin[0].scriptSig.Size < 2 || vin[0].scriptSig.Size > 100)
215                 {
216                     // Script size is invalid
217                     return false;
218                 }
219             }
220             else
221             {
222                 foreach (var txin in vin)
223                 {
224                     if (txin.prevout.IsNull)
225                     {
226                         // Null input in non-coinbase transaction.
227                         return false;
228                     }
229                 }
230             }
231
232             return true;
233         }
234
235         public bool IsFinal(uint nBlockHeight = 0, uint nBlockTime = 0)
236         {
237             // Time based nLockTime
238             if (nLockTime == 0)
239             {
240                 return true;
241             }
242             if (nBlockHeight == 0)
243             {
244                 nBlockHeight = uint.MaxValue; // TODO: stupid stub here, should be best height instead.
245             }
246             if (nBlockTime == 0)
247             {
248                 nBlockTime = NetUtils.GetAdjustedTime();
249             }
250             if (nLockTime < (nLockTime < NetUtils.nLockTimeThreshold ? nBlockHeight : nBlockTime))
251             {
252                 return true;
253             }
254             foreach (var txin in vin)
255             {
256                 if (!txin.IsFinal)
257                 {
258                     return false;
259                 }
260             }
261             return true;
262         }
263         
264         /// <summary>
265         /// Parse byte sequence and initialize new instance of CTransaction
266         /// </summary>
267         /// <param name="txBytes">Byte sequence</param>
268         public CTransaction(byte[] txBytes)
269         {
270             try
271             {
272                 var stream = new MemoryStream(txBytes);
273                 var reader = new BinaryReader(stream);
274
275                 nVersion = reader.ReadUInt32();
276                 nTime = reader.ReadUInt32();
277
278                 int nInputs = (int)VarInt.ReadVarInt(ref reader);
279                 vin = new CTxIn[nInputs];
280
281                 for (int nCurrentInput = 0; nCurrentInput < nInputs; nCurrentInput++)
282                 {
283                     // Fill inputs array
284                     vin[nCurrentInput] = new CTxIn();
285
286                     vin[nCurrentInput].prevout = new COutPoint(reader.ReadBytes(36));
287
288                     int nScriptSigLen = (int)VarInt.ReadVarInt(ref reader);
289                     vin[nCurrentInput].scriptSig = new CScript(reader.ReadBytes(nScriptSigLen));
290
291                     vin[nCurrentInput].nSequence = reader.ReadUInt32();
292                 }
293
294                 int nOutputs = (int)VarInt.ReadVarInt(ref reader);
295                 vout = new CTxOut[nOutputs];
296
297                 for (int nCurrentOutput = 0; nCurrentOutput < nOutputs; nCurrentOutput++)
298                 {
299                     // Fill outputs array
300                     vout[nCurrentOutput] = new CTxOut();
301                     vout[nCurrentOutput].nValue = reader.ReadUInt64();
302
303                     int nScriptPKLen = (int)VarInt.ReadVarInt(ref reader);
304                     vout[nCurrentOutput].scriptPubKey = new CScript(reader.ReadBytes(nScriptPKLen));
305                 }
306
307                 nLockTime = reader.ReadUInt32();
308             }
309             catch (Exception e)
310             {
311                 throw new TransactionConstructorException("Deserialization failed", e);
312             }
313         }
314
315         /// <summary>
316         /// Serialized size
317         /// </summary>
318         public int Size
319         {
320             get
321             {
322                 int nSize = 12; // nVersion, nTime, nLockLime
323
324                 nSize += VarInt.GetEncodedSize(vin.Length);
325                 nSize += VarInt.GetEncodedSize(vout.Length);
326
327                 foreach (var input in vin)
328                 {
329                     nSize += input.Size;
330                 }
331
332                 foreach (var output in vout)
333                 {
334                     nSize += output.Size;
335                 }
336
337                 return nSize;
338             }
339         }
340
341         /// <summary>
342         /// Read transactions array which is encoded in the block body.
343         /// </summary>
344         /// <param name="wTxBytes">Bytes sequence</param>
345         /// <returns>Transactions array</returns>
346         internal static CTransaction[] ReadTransactionsList(ref BinaryReader reader)
347         {
348             try
349             {
350                 // Read amount of transactions
351                 int nTransactions = (int)VarInt.ReadVarInt(ref reader);
352                 var tx = new CTransaction[nTransactions];
353
354                 for (int nTx = 0; nTx < nTransactions; nTx++)
355                 {
356                     // Fill the transactions array
357                     tx[nTx] = new CTransaction();
358
359                     tx[nTx].nVersion = reader.ReadUInt32();
360                     tx[nTx].nTime = reader.ReadUInt32();
361
362                     // Inputs array
363                     tx[nTx].vin = CTxIn.ReadTxInList(ref reader);
364
365                     // outputs array
366                     tx[nTx].vout = CTxOut.ReadTxOutList(ref reader);
367
368                     tx[nTx].nLockTime = reader.ReadUInt32();
369                 }
370
371                 return tx;
372
373             }
374             catch (Exception e)
375             {
376                 throw new TransactionConstructorException("Deserialization failed", e);
377             }
378         }
379
380         public bool IsCoinBase
381         {
382             get { return (vin.Length == 1 && vin[0].prevout.IsNull && vout.Length >= 1); }
383         }
384
385         public bool IsCoinStake
386         {
387             get
388             {
389                 return (vin.Length > 0 && (!vin[0].prevout.IsNull) && vout.Length >= 2 && vout[0].IsEmpty);
390             }
391         }
392
393         /// <summary>
394         /// Transaction hash
395         /// </summary>
396         public Hash256 Hash
397         {
398             get { return Hash256.Compute256(this); }
399         }
400
401         /// <summary>
402         /// A sequence of bytes, which corresponds to the current state of CTransaction.
403         /// </summary>
404         public static implicit operator byte[] (CTransaction tx)
405         {
406             var stream = new MemoryStream();
407             var writer = new BinaryWriter(stream);
408
409             writer.Write(tx.nVersion);
410             writer.Write(tx.nTime);
411             writer.Write(VarInt.EncodeVarInt(tx.vin.LongLength));
412
413             foreach (var input in tx.vin)
414             {
415                 writer.Write(input);
416             }
417
418             writer.Write(VarInt.EncodeVarInt(tx.vout.LongLength));
419
420             foreach (var output in tx.vout)
421             {
422                 writer.Write(output);
423             }
424
425             writer.Write(tx.nLockTime);
426             var resultBytes = stream.ToArray();
427             writer.Close();
428
429             return resultBytes;
430         }
431
432
433
434         public override string ToString()
435         {
436             var sb = new StringBuilder();
437
438             sb.AppendFormat("CTransaction(\n nVersion={0},\n nTime={1},\n", nVersion, nTime);
439
440             foreach (var txin in vin)
441             {
442                 sb.AppendFormat(" {0},\n", txin.ToString());
443             }
444
445             foreach (var txout in vout)
446             {
447                 sb.AppendFormat(" {0},\n", txout.ToString());
448             }
449
450             sb.AppendFormat("\nnLockTime={0}\n)", nLockTime);
451
452             return sb.ToString();
453         }
454
455         public static bool MoneyRange(ulong nValue) { return (nValue <= nMaxMoney); }
456     }
457 }