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