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