ConnectInputs + stubs for GetCoinAge, GetMinFee and GetProofOfStakeReward.
[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         public enum MinFeeMode
71         {
72             GMF_BLOCK,
73             GMF_RELAY,
74             GMF_SEND,
75         }
76
77         /// <summary>
78         /// Version of transaction schema.
79         /// </summary>
80         public uint nVersion;
81
82         /// <summary>
83         /// Transaction timestamp.
84         /// </summary>
85         public uint nTime;
86
87         /// <summary>
88         /// Array of transaction inputs
89         /// </summary>
90         public CTxIn[] vin;
91
92         /// <summary>
93         /// Array of transaction outputs
94         /// </summary>
95         public CTxOut[] vout;
96
97         /// <summary>
98         /// Block height or timestamp when transaction is final
99         /// </summary>
100         public uint nLockTime;
101
102         /// <summary>
103         /// Initialize an empty instance
104         /// </summary>
105         public CTransaction()
106         {
107             // Initialize empty input and output arrays. Please note that such 
108             // configuration is not valid for real transaction, you have to supply 
109             // at least one input and one output.
110             nVersion = 1;
111             nTime = 0;
112             vin = new CTxIn[0];
113             vout = new CTxOut[0];
114             nLockTime = 0;
115         }
116
117         /// <summary>
118         /// Initialize new instance as a copy of another transaction
119         /// </summary>
120         /// <param name="tx">Transaction to copy from</param>
121         public CTransaction(CTransaction tx)
122         {
123             nVersion = tx.nVersion;
124             nTime = tx.nTime;
125
126             vin = new CTxIn[tx.vin.Length];
127
128             for (int i = 0; i < vin.Length; i++)
129             {
130                 vin[i] = new CTxIn(tx.vin[i]);
131             }
132
133             vout = new CTxOut[tx.vout.Length];
134
135             for (int i = 0; i < vout.Length; i++)
136             {
137                 vout[i] = new CTxOut(tx.vout[i]);
138             }
139
140             nLockTime = tx.nLockTime;
141         }
142
143         /// <summary>
144         /// Attempts to execute all transaction scripts and validate the results.
145         /// </summary>
146         /// <returns>Checking result.</returns>
147         public bool VerifyScripts()
148         {
149             if (IsCoinBase)
150             {
151                 return true;
152             }
153
154             TxOutItem txOutCursor = null;
155             for (int i = 0; i < vin.Length; i++)
156             {
157                 var outpoint = vin[i].prevout;
158
159                 if (!CBlockStore.Instance.GetTxOutCursor(outpoint, ref txOutCursor))
160                     return false;
161
162                 if (!ScriptCode.VerifyScript(vin[i].scriptSig, txOutCursor.scriptPubKey, this, i, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0))
163                     return false;
164             }
165
166             return true;
167         }
168
169         /// <summary>
170         /// Calculate amount of signature operations without trying to properly evaluate P2SH scripts.
171         /// </summary>
172         public uint LegacySigOpCount
173         {
174             get
175             {
176                 uint nSigOps = 0;
177                 foreach (var txin in vin)
178                 {
179                     nSigOps += txin.scriptSig.GetSigOpCount(false);
180                 }
181                 foreach (var txout in vout)
182                 {
183                     nSigOps += txout.scriptPubKey.GetSigOpCount(false);
184                 }
185
186                 return nSigOps;
187             }
188         }
189
190         /// <summary>
191         /// Basic sanity checkings
192         /// </summary>
193         /// <returns>Checking result</returns>
194         public bool CheckTransaction()
195         {
196             if (Size > nMaxTxSize || vin.Length == 0 || vout.Length == 0)
197             {
198                 return false;
199             }
200
201             // Check for empty or overflow output values
202             ulong nValueOut = 0;
203             for (int i = 0; i < vout.Length; i++)
204             {
205                 CTxOut txout = vout[i];
206                 if (txout.IsEmpty && !IsCoinBase && !IsCoinStake)
207                 {
208                     // Empty outputs aren't allowed for user transactions.
209                     return false;
210                 }
211
212                 nValueOut += txout.nValue;
213                 if (!MoneyRange(nValueOut))
214                 {
215                     return false;
216                 }
217             }
218
219             // Check for duplicate inputs
220             var InOutPoints = new List<COutPoint>();
221             foreach (var txin in vin)
222             {
223                 if (InOutPoints.IndexOf(txin.prevout) != -1)
224                 {
225                     // Duplicate input.
226                     return false;
227                 }
228                 InOutPoints.Add(txin.prevout);
229             }
230
231             if (IsCoinBase)
232             {
233                 if (vin[0].scriptSig.Size < 2 || vin[0].scriptSig.Size > 100)
234                 {
235                     // Script size is invalid
236                     return false;
237                 }
238             }
239             else
240             {
241                 foreach (var txin in vin)
242                 {
243                     if (txin.prevout.IsNull)
244                     {
245                         // Null input in non-coinbase transaction.
246                         return false;
247                     }
248                 }
249             }
250
251             return true;
252         }
253
254         public bool IsFinal(uint nBlockHeight = 0, uint nBlockTime = 0)
255         {
256             // Time based nLockTime
257             if (nLockTime == 0)
258             {
259                 return true;
260             }
261             if (nBlockHeight == 0)
262             {
263                 nBlockHeight = uint.MaxValue; // TODO: stupid stub here, should be best height instead.
264             }
265             if (nBlockTime == 0)
266             {
267                 nBlockTime = NetInfo.GetAdjustedTime();
268             }
269             if (nLockTime < (nLockTime < NetInfo.nLockTimeThreshold ? nBlockHeight : nBlockTime))
270             {
271                 return true;
272             }
273             foreach (var txin in vin)
274             {
275                 if (!txin.IsFinal)
276                 {
277                     return false;
278                 }
279             }
280             return true;
281         }
282
283         /// <summary>
284         /// Parse byte sequence and initialize new instance of CTransaction
285         /// </summary>
286         /// <param name="txBytes">Byte sequence</param>
287         public CTransaction(byte[] txBytes)
288         {
289             try
290             {
291                 var stream = new MemoryStream(txBytes);
292                 var reader = new BinaryReader(stream);
293
294                 nVersion = reader.ReadUInt32();
295                 nTime = reader.ReadUInt32();
296
297                 int nInputs = (int)VarInt.ReadVarInt(ref reader);
298                 vin = new CTxIn[nInputs];
299
300                 for (int nCurrentInput = 0; nCurrentInput < nInputs; nCurrentInput++)
301                 {
302                     // Fill inputs array
303                     vin[nCurrentInput] = new CTxIn();
304
305                     vin[nCurrentInput].prevout = new COutPoint(reader.ReadBytes(36));
306
307                     int nScriptSigLen = (int)VarInt.ReadVarInt(ref reader);
308                     vin[nCurrentInput].scriptSig = new CScript(reader.ReadBytes(nScriptSigLen));
309
310                     vin[nCurrentInput].nSequence = reader.ReadUInt32();
311                 }
312
313                 int nOutputs = (int)VarInt.ReadVarInt(ref reader);
314                 vout = new CTxOut[nOutputs];
315
316                 for (int nCurrentOutput = 0; nCurrentOutput < nOutputs; nCurrentOutput++)
317                 {
318                     // Fill outputs array
319                     vout[nCurrentOutput] = new CTxOut();
320                     vout[nCurrentOutput].nValue = reader.ReadUInt64();
321
322                     int nScriptPKLen = (int)VarInt.ReadVarInt(ref reader);
323                     vout[nCurrentOutput].scriptPubKey = new CScript(reader.ReadBytes(nScriptPKLen));
324                 }
325
326                 nLockTime = reader.ReadUInt32();
327             }
328             catch (Exception e)
329             {
330                 throw new TransactionConstructorException("Deserialization failed", e);
331             }
332         }
333
334         /// <summary>
335         /// Serialized size
336         /// </summary>
337         public int Size
338         {
339             get
340             {
341                 int nSize = 12; // nVersion, nTime, nLockLime
342
343                 nSize += VarInt.GetEncodedSize(vin.Length);
344                 nSize += VarInt.GetEncodedSize(vout.Length);
345
346                 foreach (var input in vin)
347                 {
348                     nSize += input.Size;
349                 }
350
351                 foreach (var output in vout)
352                 {
353                     nSize += output.Size;
354                 }
355
356                 return nSize;
357             }
358         }
359
360         /// <summary>
361         /// Read transactions array which is encoded in the block body.
362         /// </summary>
363         /// <param name="wTxBytes">Bytes sequence</param>
364         /// <returns>Transactions array</returns>
365         internal static CTransaction[] ReadTransactionsList(ref BinaryReader reader)
366         {
367             try
368             {
369                 // Read amount of transactions
370                 int nTransactions = (int)VarInt.ReadVarInt(ref reader);
371                 var tx = new CTransaction[nTransactions];
372
373                 for (int nTx = 0; nTx < nTransactions; nTx++)
374                 {
375                     // Fill the transactions array
376                     tx[nTx] = new CTransaction();
377
378                     tx[nTx].nVersion = reader.ReadUInt32();
379                     tx[nTx].nTime = reader.ReadUInt32();
380
381                     // Inputs array
382                     tx[nTx].vin = CTxIn.ReadTxInList(ref reader);
383
384                     // outputs array
385                     tx[nTx].vout = CTxOut.ReadTxOutList(ref reader);
386
387                     tx[nTx].nLockTime = reader.ReadUInt32();
388                 }
389
390                 return tx;
391
392             }
393             catch (Exception e)
394             {
395                 throw new TransactionConstructorException("Deserialization failed", e);
396             }
397         }
398
399         public bool IsCoinBase
400         {
401             get { return (vin.Length == 1 && vin[0].prevout.IsNull && vout.Length >= 1); }
402         }
403
404         public bool IsCoinStake
405         {
406             get
407             {
408                 return (vin.Length > 0 && (!vin[0].prevout.IsNull) && vout.Length >= 2 && vout[0].IsEmpty);
409             }
410         }
411
412         /// <summary>
413         /// Transaction hash
414         /// </summary>
415         public uint256 Hash
416         {
417             get { return CryptoUtils.ComputeHash256(this); }
418         }
419
420         /// <summary>
421         /// Amount of novacoins spent by this transaction.
422         /// </summary>
423         public ulong nValueOut
424         {
425             get
426             {
427                 ulong nValueOut = 0;
428                 foreach (var txout in vout)
429                 {
430                     nValueOut += txout.nValue;
431                     Contract.Assert(MoneyRange(txout.nValue) && MoneyRange(nValueOut));
432                 }
433                 return nValueOut;
434             }
435         }
436
437         /// <summary>
438         /// A sequence of bytes, which corresponds to the current state of CTransaction.
439         /// </summary>
440         public static implicit operator byte[] (CTransaction tx)
441         {
442             var stream = new MemoryStream();
443             var writer = new BinaryWriter(stream);
444
445             writer.Write(tx.nVersion);
446             writer.Write(tx.nTime);
447             writer.Write(VarInt.EncodeVarInt(tx.vin.LongLength));
448
449             foreach (var input in tx.vin)
450             {
451                 writer.Write(input);
452             }
453
454             writer.Write(VarInt.EncodeVarInt(tx.vout.LongLength));
455
456             foreach (var output in tx.vout)
457             {
458                 writer.Write(output);
459             }
460
461             writer.Write(tx.nLockTime);
462             var resultBytes = stream.ToArray();
463             writer.Close();
464
465             return resultBytes;
466         }
467
468         public override string ToString()
469         {
470             var sb = new StringBuilder();
471
472             sb.AppendFormat("CTransaction(\n nVersion={0},\n nTime={1},\n", nVersion, nTime);
473
474             foreach (var txin in vin)
475             {
476                 sb.AppendFormat(" {0},\n", txin);
477             }
478
479             foreach (var txout in vout)
480             {
481                 sb.AppendFormat(" {0},\n", txout);
482             }
483
484             sb.AppendFormat("\nnLockTime={0}\n)", nLockTime);
485
486             return sb.ToString();
487         }
488
489         public static bool MoneyRange(ulong nValue) { return (nValue <= nMaxMoney); }
490
491         /// <summary>
492         /// Get total sigops.
493         /// </summary>
494         /// <param name="inputs">Inputs map.</param>
495         /// <returns>Amount of sigops.</returns>
496         public uint GetP2SHSigOpCount(ref Dictionary<COutPoint, TxOutItem> inputs)
497         {
498             if (IsCoinBase)
499             {
500                 return 0;
501             }
502
503             uint nSigOps = 0;
504             for (var i = 0; i < vin.Length; i++)
505             {
506                 var prevout = GetOutputFor(vin[i], ref inputs);
507                 if (prevout.scriptPubKey.IsPayToScriptHash)
508                 {
509                     nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig);
510                 }
511             }
512
513             return nSigOps;
514         }
515
516         /// <summary>
517         /// Get sum of inputs spent by this transaction.
518         /// </summary>
519         /// <param name="inputs">Reference to innputs map.</param>
520         /// <returns>Sum of inputs.</returns>
521         public ulong GetValueIn(ref Dictionary<COutPoint, TxOutItem> inputs)
522         {
523             if (IsCoinBase)
524             {
525                 return 0;
526             }
527
528             ulong nResult = 0;
529             for (int i = 0; i < vin.Length; i++)
530             {
531                 nResult += GetOutputFor(vin[i], ref inputs).nValue;
532             }
533
534             return nResult;
535         }
536
537         /// <summary>
538         /// Helper method to find output in the map.
539         /// </summary>
540         /// <param name="input">Transaction input.</param>
541         /// <param name="inputs">eference to inuts map.</param>
542         /// <returns>Parent output.</returns>
543         private CTxOut GetOutputFor(CTxIn input, ref Dictionary<COutPoint, TxOutItem> inputs)
544         {
545             if (!inputs.ContainsKey(input.prevout))
546             {
547                 throw new Exception("No such input");
548             }
549
550             var outItem = inputs[input.prevout];
551
552             return new CTxOut(outItem.nValue, outItem.scriptPubKey);
553         }
554
555         internal bool GetCoinAge(ref Dictionary<COutPoint, TxOutItem> inputs, out ulong nCoinAge)
556         {
557             throw new NotImplementedException();
558         }
559
560         internal static ulong GetMinFee(int v1, bool v2, MinFeeMode gMF_BLOCK, int nTxSize)
561         {
562             throw new NotImplementedException();
563         }
564     }
565 }