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