GetCoinAge
[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         /// <summary>
561         /// Calculate coin*age. 
562         /// 
563         /// Note, only those coins meeting minimum age requirement counts.
564         /// </summary>
565         /// <param name="inputs">Inputs set.</param>
566         /// <param name="nCoinAge">Coin age calculation result.</param>
567         /// <returns>Result</returns>
568         public bool GetCoinAge(ref Dictionary<COutPoint, TxOutItem> inputs, out ulong nCoinAge)
569         {
570             BigInteger bnCentSecond = 0;  // coin age in the unit of cent-seconds
571             nCoinAge = 0;
572
573             if (IsCoinBase)
574             {
575                 // Nothing spent by coinbase, coinage is always zero.
576                 return true;
577             }
578
579             for( var i = 0; i<vin.Length; i++)
580             {
581                 var prevout = vin[i].prevout;
582                 Contract.Assert(inputs.ContainsKey(prevout));
583                 var input = inputs[prevout];
584
585                 CBlockStoreItem parentBlockCursor;
586                 var merkleItem = CBlockStore.Instance.GetMerkleCursor(input, out parentBlockCursor);
587
588                 if (merkleItem == null)
589                 {
590                     return false; // Unable to find merkle node
591                 }
592
593                 if (nTime < merkleItem.nTime)
594                 {
595                     return false;  // Transaction timestamp violation
596                 }
597
598                 if (parentBlockCursor.nTime + StakeModifier.nStakeMinAge > nTime)
599                 {
600                     continue; // only count coins meeting min age requirement
601                 }
602
603                 ulong nValueIn = input.nValue;
604                 bnCentSecond += new BigInteger(nValueIn) * (nTime - merkleItem.nTime) / nCent;
605             }
606
607             BigInteger bnCoinDay = bnCentSecond * nCent / nCoin / (24 * 60 * 60);
608             nCoinAge = (ulong)bnCoinDay;
609
610             return true;
611         }
612
613         public ulong GetMinFee(uint nBlockSize, bool fAllowFree, MinFeeMode mode)
614         {
615             ulong nMinTxFee = CTransaction.nMinTxFee, nMinRelayTxFee = CTransaction.nMinRelayTxFee;
616             uint nBytes = Size;
617
618             if (IsCoinStake)
619             {
620                 // Enforce 0.01 as minimum fee for old approach or coinstake
621                 nMinTxFee = nCent;
622                 nMinRelayTxFee = nCent;
623
624                 if (nTime < NetInfo.nStakeValidationSwitchTime)
625                 {
626                     // Enforce zero size for compatibility with old blocks.
627                     nBytes = 0;
628                 }
629             }
630
631             // Base fee is either nMinTxFee or nMinRelayTxFee
632             ulong nBaseFee = (mode == MinFeeMode.GMF_RELAY) ? nMinRelayTxFee : nMinTxFee;
633
634             uint nNewBlockSize = nBlockSize + nBytes;
635             ulong nMinFee = (1 + (ulong)nBytes / 1000) * nBaseFee;
636
637             if (fAllowFree)
638             {
639                 if (nBlockSize == 1)
640                 {
641                     // Transactions under 1K are free
642                     if (nBytes < 1000)
643                         nMinFee = 0;
644                 }
645                 else
646                 {
647                     // Free transaction area
648                     if (nNewBlockSize < 27000)
649                         nMinFee = 0;
650                 }
651             }
652
653             // To limit dust spam, require additional MIN_TX_FEE/MIN_RELAY_TX_FEE for
654             //    each non empty output which is less than 0.01
655             //
656             // It's safe to ignore empty outputs here, because these inputs are allowed
657             //     only for coinbase and coinstake transactions.
658             foreach (var txout in vout)
659             {
660                 if (txout.nValue < nCent && !txout.IsEmpty)
661                 {
662                     nMinFee += nBaseFee;
663                 }
664             }
665
666             var nMaxBlockSizeGen = CBlock.nMaxBlockSize / 2;
667
668             // Raise the price as the block approaches full
669             if (nBlockSize != 1 && nNewBlockSize >= nMaxBlockSizeGen / 2)
670             {
671                 if (nNewBlockSize >= nMaxBlockSizeGen)
672                 {
673                     return nMaxMoney;
674                 }
675
676                 nMinFee *= nMaxBlockSizeGen / (nMaxBlockSizeGen - nNewBlockSize);
677             }
678
679             if (!MoneyRange(nMinFee))
680             {
681                 nMinFee = nMaxMoney;
682             }
683
684             return nMinFee;
685         }
686     }
687 }