Turn ByteQueue into MemoryStream wrapper, use MemoryStream for serialization of COutP...
[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 wBytes = new ByteQueue(ref txBytes);
273
274                 nVersion = BitConverter.ToUInt32(wBytes.Get(4), 0);
275                 nTime = BitConverter.ToUInt32(wBytes.Get(4), 0);
276
277                 int nInputs = (int)wBytes.GetVarInt();
278                 vin = new CTxIn[nInputs];
279
280                 for (int nCurrentInput = 0; nCurrentInput < nInputs; nCurrentInput++)
281                 {
282                     // Fill inputs array
283                     vin[nCurrentInput] = new CTxIn();
284
285                     vin[nCurrentInput].prevout = new COutPoint(wBytes.Get(36));
286
287                     int nScriptSigLen = (int)wBytes.GetVarInt();
288                     vin[nCurrentInput].scriptSig = new CScript(wBytes.Get(nScriptSigLen));
289
290                     vin[nCurrentInput].nSequence = BitConverter.ToUInt32(wBytes.Get(4), 0);
291                 }
292
293                 int nOutputs = (int)wBytes.GetVarInt();
294                 vout = new CTxOut[nOutputs];
295
296                 for (int nCurrentOutput = 0; nCurrentOutput < nOutputs; nCurrentOutput++)
297                 {
298                     // Fill outputs array
299                     vout[nCurrentOutput] = new CTxOut();
300                     vout[nCurrentOutput].nValue = BitConverter.ToUInt64(wBytes.Get(8), 0);
301
302                     int nScriptPKLen = (int)wBytes.GetVarInt();
303                     vout[nCurrentOutput].scriptPubKey = new CScript(wBytes.Get(nScriptPKLen));
304                 }
305
306                 nLockTime = BitConverter.ToUInt32(wBytes.Get(4), 0);
307             }
308             catch (Exception e)
309             {
310                 throw new TransactionConstructorException("Deserialization failed", e);
311             }
312         }
313
314         /// <summary>
315         /// Serialized size
316         /// </summary>
317         public int Size
318         {
319             get
320             {
321                 int nSize = 12; // nVersion, nTime, nLockLime
322
323                 nSize += VarInt.GetEncodedSize(vin.Length);
324                 nSize += VarInt.GetEncodedSize(vout.Length);
325
326                 foreach (var input in vin)
327                 {
328                     nSize += input.Size;
329                 }
330
331                 foreach (var output in vout)
332                 {
333                     nSize += output.Size;
334                 }
335
336                 return nSize;
337             }
338         }
339
340         /// <summary>
341         /// Read transactions array which is encoded in the block body.
342         /// </summary>
343         /// <param name="wTxBytes">Bytes sequence</param>
344         /// <returns>Transactions array</returns>
345         public static CTransaction[] ReadTransactionsList(ref ByteQueue wTxBytes)
346         {
347             try
348             {
349                 // Read amount of transactions
350                 int nTransactions = (int)wTxBytes.GetVarInt();
351                 var tx = new CTransaction[nTransactions];
352
353                 for (int nTx = 0; nTx < nTransactions; nTx++)
354                 {
355                     // Fill the transactions array
356                     tx[nTx] = new CTransaction();
357
358                     tx[nTx].nVersion = BitConverter.ToUInt32(wTxBytes.Get(4), 0);
359                     tx[nTx].nTime = BitConverter.ToUInt32(wTxBytes.Get(4), 0);
360
361                     // Inputs array
362                     tx[nTx].vin = CTxIn.ReadTxInList(ref wTxBytes);
363
364                     // outputs array
365                     tx[nTx].vout = CTxOut.ReadTxOutList(ref wTxBytes);
366
367                     tx[nTx].nLockTime = BitConverter.ToUInt32(wTxBytes.Get(4), 0);
368                 }
369
370                 return tx;
371
372             }
373             catch (Exception e)
374             {
375                 throw new TransactionConstructorException("Deserialization failed", e);
376             }
377         }
378
379         public bool IsCoinBase
380         {
381             get { return (vin.Length == 1 && vin[0].prevout.IsNull && vout.Length >= 1); }
382         }
383
384         public bool IsCoinStake
385         {
386             get
387             {
388                 return (vin.Length > 0 && (!vin[0].prevout.IsNull) && vout.Length >= 2 && vout[0].IsEmpty);
389             }
390         }
391
392         /// <summary>
393         /// Transaction hash
394         /// </summary>
395         public Hash256 Hash
396         {
397             get { return Hash256.Compute256(this); }
398         }
399
400         /// <summary>
401         /// A sequence of bytes, which corresponds to the current state of CTransaction.
402         /// </summary>
403         public static implicit operator byte[] (CTransaction tx)
404         {
405             var stream = new MemoryStream();
406             var writer = new BinaryWriter(stream);
407
408             writer.Write(tx.nVersion);
409             writer.Write(tx.nTime);
410             writer.Write(VarInt.EncodeVarInt(tx.vin.LongLength));
411
412             foreach (var input in tx.vin)
413             {
414                 writer.Write(input);
415             }
416
417             writer.Write(VarInt.EncodeVarInt(tx.vout.LongLength));
418
419             foreach (var output in tx.vout)
420             {
421                 writer.Write(output);
422             }
423
424             writer.Write(tx.nLockTime);
425
426             var resultBytes = stream.ToArray();
427
428             writer.Close();
429
430             return resultBytes;
431         }
432
433
434
435         public override string ToString()
436         {
437             var sb = new StringBuilder();
438
439             sb.AppendFormat("CTransaction(\n nVersion={0},\n nTime={1},\n", nVersion, nTime);
440
441             foreach (var txin in vin)
442             {
443                 sb.AppendFormat(" {0},\n", txin.ToString());
444             }
445
446             foreach (var txout in vout)
447             {
448                 sb.AppendFormat(" {0},\n", txout.ToString());
449             }
450
451             sb.AppendFormat("\nnLockTime={0}\n)", nLockTime);
452
453             return sb.ToString();
454         }
455
456         public static bool MoneyRange(ulong nValue) { return (nValue <= nMaxMoney); }
457     }
458 }