2 * Novacoin classes library
3 * Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
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.
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.
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/>.
21 using System.Collections.Generic;
23 using System.Diagnostics.Contracts;
24 using System.Numerics;
29 public class TransactionConstructorException : Exception
31 public TransactionConstructorException()
35 public TransactionConstructorException(string message)
40 public TransactionConstructorException(string message, Exception inner)
41 : base(message, inner)
47 /// Represents the transaction.
49 public class CTransaction
52 /// One cent = 10000 satoshis.
54 public const long nCent = 10000;
57 /// One coin = 1000000 satoshis.
59 public const long nCoin = 1000000;
62 /// Sanity checking threshold.
64 public const long nMaxMoney = 2000000000 * nCoin;
66 public const long nMinTxFee = nCent / 10;
67 public const long nMinRelayTxFee = nCent / 50;
68 public const long nMinTxoutAmount = nCent / 100;
71 /// Maximum transaction size is 250Kb
73 public const uint nMaxTxSize = 250000;
75 public enum MinFeeMode
83 /// Version of transaction schema.
88 /// Transaction timestamp.
93 /// Array of transaction inputs
98 /// Array of transaction outputs
100 public CTxOut[] vout;
103 /// Block height or timestamp when transaction is final
105 public uint nLockTime;
108 /// Initialize an empty instance
110 public CTransaction()
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.
118 vout = new CTxOut[0];
123 /// Initialize new instance as a copy of another transaction
125 /// <param name="tx">Transaction to copy from</param>
126 public CTransaction(CTransaction tx)
128 nVersion = tx.nVersion;
131 vin = new CTxIn[tx.vin.Length];
133 for (int i = 0; i < vin.Length; i++)
135 vin[i] = new CTxIn(tx.vin[i]);
138 vout = new CTxOut[tx.vout.Length];
140 for (int i = 0; i < vout.Length; i++)
142 vout[i] = new CTxOut(tx.vout[i]);
145 nLockTime = tx.nLockTime;
149 /// Attempts to execute all transaction scripts and validate the results.
151 /// <returns>Checking result.</returns>
152 public bool VerifyScripts()
159 for (int i = 0; i < vin.Length; i++)
161 var outpoint = vin[i].prevout;
163 TxOutItem txOutCursor;
164 if (!CBlockStore.Instance.GetTxOutCursor(outpoint, out txOutCursor))
167 if (!ScriptCode.VerifyScript(vin[i].scriptSig, txOutCursor.scriptPubKey, this, i, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0))
175 /// Calculate amount of signature operations without trying to properly evaluate P2SH scripts.
177 public uint LegacySigOpCount
185 // http://lists.linuxfoundation.org/pipermail/bitcoin-dev/2012-July/001718.html
187 foreach (var txin in vin)
189 nSigOps += txin.scriptSig.GetSigOpCount(false);
193 foreach (var txout in vout)
195 nSigOps += txout.scriptPubKey.GetSigOpCount(false);
203 /// Basic sanity checkings
205 /// <returns>Checking result</returns>
206 public bool CheckTransaction()
208 if (Size > nMaxTxSize || vin.Length == 0 || vout.Length == 0)
213 // Check for empty or overflow output values
215 for (int i = 0; i < vout.Length; i++)
217 CTxOut txout = vout[i];
218 if (txout.IsEmpty && !IsCoinBase && !IsCoinStake)
220 // Empty outputs aren't allowed for user transactions.
224 nValueOut += txout.nValue;
225 if (!MoneyRange(nValueOut))
231 // Check for duplicate inputs
232 var InOutPoints = new List<COutPoint>();
233 foreach (var txin in vin)
235 if (InOutPoints.IndexOf(txin.prevout) != -1)
240 InOutPoints.Add(txin.prevout);
245 if (vin[0].scriptSig.Size < 2 || vin[0].scriptSig.Size > 100)
247 // Script size is invalid
253 foreach (var txin in vin)
255 if (txin.prevout.IsNull)
257 // Null input in non-coinbase transaction.
266 public bool IsFinal(uint nBlockHeight = 0, uint nBlockTime = 0)
268 // Time based nLockTime
273 if (nBlockHeight == 0)
275 nBlockHeight = uint.MaxValue; // TODO: stupid stub here, should be best height instead.
279 nBlockTime = NetInfo.GetAdjustedTime();
281 if (nLockTime < (nLockTime < NetInfo.nLockTimeThreshold ? nBlockHeight : nBlockTime))
285 foreach (var txin in vin)
296 /// Parse byte sequence and initialize new instance of CTransaction
298 /// <param name="txBytes">Byte sequence</param>
299 public CTransaction(byte[] txBytes)
303 var stream = new MemoryStream(txBytes);
304 var reader = new BinaryReader(stream);
306 nVersion = reader.ReadUInt32();
307 nTime = reader.ReadUInt32();
309 int nInputs = (int)VarInt.ReadVarInt(ref reader);
310 vin = new CTxIn[nInputs];
312 for (int nCurrentInput = 0; nCurrentInput < nInputs; nCurrentInput++)
315 vin[nCurrentInput] = new CTxIn();
317 vin[nCurrentInput].prevout = new COutPoint(reader.ReadBytes(36));
319 int nScriptSigLen = (int)VarInt.ReadVarInt(ref reader);
320 vin[nCurrentInput].scriptSig = new CScript(reader.ReadBytes(nScriptSigLen));
322 vin[nCurrentInput].nSequence = reader.ReadUInt32();
325 int nOutputs = (int)VarInt.ReadVarInt(ref reader);
326 vout = new CTxOut[nOutputs];
328 for (int nCurrentOutput = 0; nCurrentOutput < nOutputs; nCurrentOutput++)
330 // Fill outputs array
331 vout[nCurrentOutput] = new CTxOut();
332 vout[nCurrentOutput].nValue = reader.ReadInt64();
334 int nScriptPKLen = (int)VarInt.ReadVarInt(ref reader);
335 vout[nCurrentOutput].scriptPubKey = new CScript(reader.ReadBytes(nScriptPKLen));
338 nLockTime = reader.ReadUInt32();
342 throw new TransactionConstructorException("Deserialization failed", e);
353 uint nSize = 12; // nVersion, nTime, nLockLime
355 nSize += VarInt.GetEncodedSize(vin.Length);
356 nSize += VarInt.GetEncodedSize(vout.Length);
358 foreach (var input in vin)
363 foreach (var output in vout)
365 nSize += output.Size;
373 /// Read transactions array which is encoded in the block body.
375 /// <param name="wTxBytes">Bytes sequence</param>
376 /// <returns>Transactions array</returns>
377 internal static CTransaction[] ReadTransactionsList(ref BinaryReader reader)
381 // Read amount of transactions
382 int nTransactions = (int)VarInt.ReadVarInt(ref reader);
383 var tx = new CTransaction[nTransactions];
385 for (int nTx = 0; nTx < nTransactions; nTx++)
387 // Fill the transactions array
388 tx[nTx] = new CTransaction();
390 tx[nTx].nVersion = reader.ReadUInt32();
391 tx[nTx].nTime = reader.ReadUInt32();
394 tx[nTx].vin = CTxIn.ReadTxInList(ref reader);
397 tx[nTx].vout = CTxOut.ReadTxOutList(ref reader);
399 tx[nTx].nLockTime = reader.ReadUInt32();
407 throw new TransactionConstructorException("Deserialization failed", e);
411 public bool IsCoinBase
413 get { return (vin.Length == 1 && vin[0].prevout.IsNull && vout.Length >= 1); }
416 public bool IsCoinStake
420 return (vin.Length > 0 && (!vin[0].prevout.IsNull) && vout.Length >= 2 && vout[0].IsEmpty);
429 get { return CryptoUtils.ComputeHash256(this); }
433 /// Amount of novacoins spent by this transaction.
435 public long nValueOut
440 foreach (var txout in vout)
442 nValueOut += txout.nValue;
443 Contract.Assert(MoneyRange(txout.nValue) && MoneyRange(nValueOut));
450 /// A sequence of bytes, which corresponds to the current state of CTransaction.
452 public static implicit operator byte[] (CTransaction tx)
454 var stream = new MemoryStream();
455 var writer = new BinaryWriter(stream);
457 writer.Write(tx.nVersion);
458 writer.Write(tx.nTime);
459 writer.Write(VarInt.EncodeVarInt(tx.vin.LongLength));
461 foreach (var input in tx.vin)
466 writer.Write(VarInt.EncodeVarInt(tx.vout.LongLength));
468 foreach (var output in tx.vout)
470 writer.Write(output);
473 writer.Write(tx.nLockTime);
474 var resultBytes = stream.ToArray();
480 public override string ToString()
482 var sb = new StringBuilder();
484 sb.AppendFormat("CTransaction(\n nVersion={0},\n nTime={1},\n", nVersion, nTime);
486 foreach (var txin in vin)
488 sb.AppendFormat(" {0},\n", txin);
491 foreach (var txout in vout)
493 sb.AppendFormat(" {0},\n", txout);
496 sb.AppendFormat("\nnLockTime={0}\n)", nLockTime);
498 return sb.ToString();
501 public static bool MoneyRange(long nValue) { return (nValue <= nMaxMoney); }
504 /// Get total sigops.
506 /// <param name="inputs">Inputs map.</param>
507 /// <returns>Amount of sigops.</returns>
508 public uint GetP2SHSigOpCount(ref Dictionary<COutPoint, TxOutItem> inputs)
516 for (var i = 0; i < vin.Length; i++)
518 var prevout = GetOutputFor(vin[i], ref inputs);
519 if (prevout.scriptPubKey.IsPayToScriptHash)
521 nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig);
529 /// Get sum of inputs spent by this transaction.
531 /// <param name="inputs">Reference to innputs map.</param>
532 /// <returns>Sum of inputs.</returns>
533 public long GetValueIn(ref Dictionary<COutPoint, TxOutItem> inputs)
541 for (int i = 0; i < vin.Length; i++)
543 nResult += GetOutputFor(vin[i], ref inputs).nValue;
550 /// Helper method to find output in the map.
552 /// <param name="input">Transaction input.</param>
553 /// <param name="inputs">eference to inuts map.</param>
554 /// <returns>Parent output.</returns>
555 private CTxOut GetOutputFor(CTxIn input, ref Dictionary<COutPoint, TxOutItem> inputs)
557 if (!inputs.ContainsKey(input.prevout))
559 throw new Exception("No such input");
562 var outItem = inputs[input.prevout];
564 return new CTxOut(outItem.nValue, outItem.scriptPubKey);
568 /// Calculate coin*age.
570 /// Note, only those coins meeting minimum age requirement counts.
572 /// <param name="inputs">Inputs set.</param>
573 /// <param name="nCoinAge">Coin age calculation result.</param>
574 /// <returns>Result</returns>
575 public bool GetCoinAge(ref Dictionary<COutPoint, TxOutItem> inputs, out long nCoinAge)
577 BigInteger bnCentSecond = 0; // coin age in the unit of cent-seconds
582 // Nothing spent by coinbase, coinage is always zero.
586 for( var i = 0; i<vin.Length; i++)
588 var prevout = vin[i].prevout;
589 Contract.Assert(inputs.ContainsKey(prevout));
590 var input = inputs[prevout];
592 CBlockStoreItem parentBlockCursor;
593 var merkleItem = CBlockStore.Instance.GetMerkleCursor(input, out parentBlockCursor);
595 if (merkleItem == null)
597 return false; // Unable to find merkle node
600 if (nTime < merkleItem.nTime)
602 return false; // Transaction timestamp violation
605 if (parentBlockCursor.nTime + StakeModifier.nStakeMinAge > nTime)
607 continue; // only count coins meeting min age requirement
610 long nValueIn = input.nValue;
611 bnCentSecond += new BigInteger(nValueIn) * (nTime - merkleItem.nTime) / nCent;
614 BigInteger bnCoinDay = bnCentSecond * nCent / nCoin / (24 * 60 * 60);
615 nCoinAge = (long)bnCoinDay;
620 public long GetMinFee(uint nBlockSize, bool fAllowFree, MinFeeMode mode)
622 long nMinTxFee = CTransaction.nMinTxFee, nMinRelayTxFee = CTransaction.nMinRelayTxFee;
627 // Enforce 0.01 as minimum fee for old approach or coinstake
629 nMinRelayTxFee = nCent;
632 // Base fee is either nMinTxFee or nMinRelayTxFee
633 long nBaseFee = (mode == MinFeeMode.GMF_RELAY) ? nMinRelayTxFee : nMinTxFee;
635 uint nNewBlockSize = nBlockSize + nBytes;
636 long nMinFee = (1 + (long)nBytes / 1000) * nBaseFee;
642 // Transactions under 1K are free
648 // Free transaction area
649 if (nNewBlockSize < 27000)
654 // To limit dust spam, require additional MIN_TX_FEE/MIN_RELAY_TX_FEE for
655 // each non empty output which is less than 0.01
657 // It's safe to ignore empty outputs here, because these inputs are allowed
658 // only for coinbase and coinstake transactions.
659 foreach (var txout in vout)
661 if (txout.nValue < nCent && !txout.IsEmpty)
667 var nMaxBlockSizeGen = CBlock.nMaxBlockSize / 2;
669 // Raise the price as the block approaches full
670 if (nBlockSize != 1 && nNewBlockSize >= nMaxBlockSizeGen / 2)
672 if (nNewBlockSize >= nMaxBlockSizeGen)
677 nMinFee *= nMaxBlockSizeGen / (nMaxBlockSizeGen - nNewBlockSize);
680 if (!MoneyRange(nMinFee))