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 ulong nCent = 10000;
57 /// One coin = 1000000 satoshis.
59 public const ulong nCoin = 1000000;
62 /// Sanity checking threshold.
64 public const ulong nMaxMoney = 2000000000 * nCoin;
66 public const ulong nMinTxFee = nCent / 10;
67 public const ulong nMinRelayTxFee = nCent / 50;
68 public const ulong 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 TxOutItem txOutCursor = null;
160 for (int i = 0; i < vin.Length; i++)
162 var outpoint = vin[i].prevout;
164 if (!CBlockStore.Instance.GetTxOutCursor(outpoint, ref 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
182 foreach (var txin in vin)
184 nSigOps += txin.scriptSig.GetSigOpCount(false);
186 foreach (var txout in vout)
188 nSigOps += txout.scriptPubKey.GetSigOpCount(false);
196 /// Basic sanity checkings
198 /// <returns>Checking result</returns>
199 public bool CheckTransaction()
201 if (Size > nMaxTxSize || vin.Length == 0 || vout.Length == 0)
206 // Check for empty or overflow output values
208 for (int i = 0; i < vout.Length; i++)
210 CTxOut txout = vout[i];
211 if (txout.IsEmpty && !IsCoinBase && !IsCoinStake)
213 // Empty outputs aren't allowed for user transactions.
217 nValueOut += txout.nValue;
218 if (!MoneyRange(nValueOut))
224 // Check for duplicate inputs
225 var InOutPoints = new List<COutPoint>();
226 foreach (var txin in vin)
228 if (InOutPoints.IndexOf(txin.prevout) != -1)
233 InOutPoints.Add(txin.prevout);
238 if (vin[0].scriptSig.Size < 2 || vin[0].scriptSig.Size > 100)
240 // Script size is invalid
246 foreach (var txin in vin)
248 if (txin.prevout.IsNull)
250 // Null input in non-coinbase transaction.
259 public bool IsFinal(uint nBlockHeight = 0, uint nBlockTime = 0)
261 // Time based nLockTime
266 if (nBlockHeight == 0)
268 nBlockHeight = uint.MaxValue; // TODO: stupid stub here, should be best height instead.
272 nBlockTime = NetInfo.GetAdjustedTime();
274 if (nLockTime < (nLockTime < NetInfo.nLockTimeThreshold ? nBlockHeight : nBlockTime))
278 foreach (var txin in vin)
289 /// Parse byte sequence and initialize new instance of CTransaction
291 /// <param name="txBytes">Byte sequence</param>
292 public CTransaction(byte[] txBytes)
296 var stream = new MemoryStream(txBytes);
297 var reader = new BinaryReader(stream);
299 nVersion = reader.ReadUInt32();
300 nTime = reader.ReadUInt32();
302 int nInputs = (int)VarInt.ReadVarInt(ref reader);
303 vin = new CTxIn[nInputs];
305 for (int nCurrentInput = 0; nCurrentInput < nInputs; nCurrentInput++)
308 vin[nCurrentInput] = new CTxIn();
310 vin[nCurrentInput].prevout = new COutPoint(reader.ReadBytes(36));
312 int nScriptSigLen = (int)VarInt.ReadVarInt(ref reader);
313 vin[nCurrentInput].scriptSig = new CScript(reader.ReadBytes(nScriptSigLen));
315 vin[nCurrentInput].nSequence = reader.ReadUInt32();
318 int nOutputs = (int)VarInt.ReadVarInt(ref reader);
319 vout = new CTxOut[nOutputs];
321 for (int nCurrentOutput = 0; nCurrentOutput < nOutputs; nCurrentOutput++)
323 // Fill outputs array
324 vout[nCurrentOutput] = new CTxOut();
325 vout[nCurrentOutput].nValue = reader.ReadUInt64();
327 int nScriptPKLen = (int)VarInt.ReadVarInt(ref reader);
328 vout[nCurrentOutput].scriptPubKey = new CScript(reader.ReadBytes(nScriptPKLen));
331 nLockTime = reader.ReadUInt32();
335 throw new TransactionConstructorException("Deserialization failed", e);
346 uint nSize = 12; // nVersion, nTime, nLockLime
348 nSize += VarInt.GetEncodedSize(vin.Length);
349 nSize += VarInt.GetEncodedSize(vout.Length);
351 foreach (var input in vin)
356 foreach (var output in vout)
358 nSize += output.Size;
366 /// Read transactions array which is encoded in the block body.
368 /// <param name="wTxBytes">Bytes sequence</param>
369 /// <returns>Transactions array</returns>
370 internal static CTransaction[] ReadTransactionsList(ref BinaryReader reader)
374 // Read amount of transactions
375 int nTransactions = (int)VarInt.ReadVarInt(ref reader);
376 var tx = new CTransaction[nTransactions];
378 for (int nTx = 0; nTx < nTransactions; nTx++)
380 // Fill the transactions array
381 tx[nTx] = new CTransaction();
383 tx[nTx].nVersion = reader.ReadUInt32();
384 tx[nTx].nTime = reader.ReadUInt32();
387 tx[nTx].vin = CTxIn.ReadTxInList(ref reader);
390 tx[nTx].vout = CTxOut.ReadTxOutList(ref reader);
392 tx[nTx].nLockTime = reader.ReadUInt32();
400 throw new TransactionConstructorException("Deserialization failed", e);
404 public bool IsCoinBase
406 get { return (vin.Length == 1 && vin[0].prevout.IsNull && vout.Length >= 1); }
409 public bool IsCoinStake
413 return (vin.Length > 0 && (!vin[0].prevout.IsNull) && vout.Length >= 2 && vout[0].IsEmpty);
422 get { return CryptoUtils.ComputeHash256(this); }
426 /// Amount of novacoins spent by this transaction.
428 public ulong nValueOut
433 foreach (var txout in vout)
435 nValueOut += txout.nValue;
436 Contract.Assert(MoneyRange(txout.nValue) && MoneyRange(nValueOut));
443 /// A sequence of bytes, which corresponds to the current state of CTransaction.
445 public static implicit operator byte[] (CTransaction tx)
447 var stream = new MemoryStream();
448 var writer = new BinaryWriter(stream);
450 writer.Write(tx.nVersion);
451 writer.Write(tx.nTime);
452 writer.Write(VarInt.EncodeVarInt(tx.vin.LongLength));
454 foreach (var input in tx.vin)
459 writer.Write(VarInt.EncodeVarInt(tx.vout.LongLength));
461 foreach (var output in tx.vout)
463 writer.Write(output);
466 writer.Write(tx.nLockTime);
467 var resultBytes = stream.ToArray();
473 public override string ToString()
475 var sb = new StringBuilder();
477 sb.AppendFormat("CTransaction(\n nVersion={0},\n nTime={1},\n", nVersion, nTime);
479 foreach (var txin in vin)
481 sb.AppendFormat(" {0},\n", txin);
484 foreach (var txout in vout)
486 sb.AppendFormat(" {0},\n", txout);
489 sb.AppendFormat("\nnLockTime={0}\n)", nLockTime);
491 return sb.ToString();
494 public static bool MoneyRange(ulong nValue) { return (nValue <= nMaxMoney); }
497 /// Get total sigops.
499 /// <param name="inputs">Inputs map.</param>
500 /// <returns>Amount of sigops.</returns>
501 public uint GetP2SHSigOpCount(ref Dictionary<COutPoint, TxOutItem> inputs)
509 for (var i = 0; i < vin.Length; i++)
511 var prevout = GetOutputFor(vin[i], ref inputs);
512 if (prevout.scriptPubKey.IsPayToScriptHash)
514 nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig);
522 /// Get sum of inputs spent by this transaction.
524 /// <param name="inputs">Reference to innputs map.</param>
525 /// <returns>Sum of inputs.</returns>
526 public ulong GetValueIn(ref Dictionary<COutPoint, TxOutItem> inputs)
534 for (int i = 0; i < vin.Length; i++)
536 nResult += GetOutputFor(vin[i], ref inputs).nValue;
543 /// Helper method to find output in the map.
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)
550 if (!inputs.ContainsKey(input.prevout))
552 throw new Exception("No such input");
555 var outItem = inputs[input.prevout];
557 return new CTxOut(outItem.nValue, outItem.scriptPubKey);
560 internal bool GetCoinAge(ref Dictionary<COutPoint, TxOutItem> inputs, out ulong nCoinAge)
562 throw new NotImplementedException();
565 public ulong GetMinFee(uint nBlockSize, bool fAllowFree, MinFeeMode mode)
567 ulong nMinTxFee = CTransaction.nMinTxFee, nMinRelayTxFee = CTransaction.nMinRelayTxFee;
572 // Enforce 0.01 as minimum fee for old approach or coinstake
574 nMinRelayTxFee = nCent;
576 if (nTime < NetInfo.nStakeValidationSwitchTime)
578 // Enforce zero size for compatibility with old blocks.
583 // Base fee is either nMinTxFee or nMinRelayTxFee
584 ulong nBaseFee = (mode == MinFeeMode.GMF_RELAY) ? nMinRelayTxFee : nMinTxFee;
586 uint nNewBlockSize = nBlockSize + nBytes;
587 ulong nMinFee = (1 + (ulong)nBytes / 1000) * nBaseFee;
593 // Transactions under 1K are free
599 // Free transaction area
600 if (nNewBlockSize < 27000)
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
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)
612 if (txout.nValue < nCent && !txout.IsEmpty)
618 var nMaxBlockSizeGen = CBlock.nMaxBlockSize / 2;
620 // Raise the price as the block approaches full
621 if (nBlockSize != 1 && nNewBlockSize >= nMaxBlockSizeGen / 2)
623 if (nNewBlockSize >= nMaxBlockSizeGen)
628 nMinFee *= nMaxBlockSizeGen / (nMaxBlockSizeGen - nNewBlockSize);
631 if (!MoneyRange(nMinFee))