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