From 183708642533185adadd50c36470927d8dd4b914 Mon Sep 17 00:00:00 2001 From: CryptoManiac Date: Mon, 7 Sep 2015 20:27:36 +0300 Subject: [PATCH] ConnectInputs + stubs for GetCoinAge, GetMinFee and GetProofOfStakeReward. --- Novacoin/CBlock.cs | 5 + Novacoin/CBlockStore.cs | 197 +++++++++++++++++++++++++++++++++++++----- Novacoin/CTransaction.cs | 16 ++++ Novacoin/DatabaseObjects.cs | 28 ++++++- Novacoin/NetInfo.cs | 16 +++- 5 files changed, 232 insertions(+), 30 deletions(-) diff --git a/Novacoin/CBlock.cs b/Novacoin/CBlock.cs index e8f2207..bf487cf 100644 --- a/Novacoin/CBlock.cs +++ b/Novacoin/CBlock.cs @@ -516,6 +516,11 @@ namespace Novacoin return Math.Min(nSubsidy, NetInfo.nMaxMintProofOfWork) + nFees; } + + internal static ulong GetProofOfStakeReward(ulong nCoinAge, uint nBits, uint nTime) + { + throw new NotImplementedException(); + } } } diff --git a/Novacoin/CBlockStore.cs b/Novacoin/CBlockStore.cs index fee3298..97ce54e 100644 --- a/Novacoin/CBlockStore.cs +++ b/Novacoin/CBlockStore.cs @@ -26,6 +26,8 @@ using SQLite.Net.Interop; using SQLite.Net.Platform.Generic; using System.Collections.Generic; using System.Text; +using System.Diagnostics.Contracts; +using System.Linq; namespace Novacoin { @@ -224,8 +226,7 @@ namespace Novacoin return false; } - - + public bool FetchInputs(CTransaction tx, ref Dictionary queued, ref Dictionary inputs, bool IsBlock, out bool Invalid) { Invalid = false; @@ -233,17 +234,17 @@ namespace Novacoin if (tx.IsCoinBase) { // Coinbase transactions have no inputs to fetch. - return true; + return true; } StringBuilder queryBuilder = new StringBuilder(); - + queryBuilder.Append("select o.*, m.[TransactionHash] from [Outputs] o left join [MerkleNodes] m on (m.[nMerkleNodeID] = o.[nMerkleNodeID]) where "); for (var i = 0; i < tx.vin.Length; i++) { - queryBuilder.AppendFormat(" {0} (m.[TransactionHash] = x'{1}' and o.[OutputNumber] = x'{2}')", - (i > 0 ? "or" : string.Empty), Interop.ToHex(tx.vin[i].prevout.hash), + queryBuilder.AppendFormat(" {0} (m.[TransactionHash] = x'{1}' and o.[OutputNumber] = x'{2}')", + (i > 0 ? "or" : string.Empty), Interop.ToHex(tx.vin[i].prevout.hash), Interop.ToHex(VarInt.EncodeVarInt(tx.vin[i].prevout.n) )); } @@ -257,12 +258,12 @@ namespace Novacoin return false; // Already spent } - var inputsKey = new COutPoint(item.TransactionHash, item.nOut); + var inputsKey = new COutPoint(item.TransactionHash, item.nOut); item.IsSpent = true; // Add output data to dictionary - inputs.Add(inputsKey, (TxOutItem) item); + inputs.Add(inputsKey, (TxOutItem)item); } if (queryResults.Count < tx.vin.Length) @@ -284,7 +285,7 @@ namespace Novacoin inputs.Add(outPoint, queued[outPoint]); // Mark output as spent - queued[outPoint].IsSpent = true; + queued[outPoint].IsSpent = true; } } else @@ -307,7 +308,7 @@ namespace Novacoin return false; // nOut is out of range } - + // TODO: return inputs from map throw new NotImplementedException(); @@ -386,7 +387,7 @@ namespace Novacoin // Get last RowID. itemTemplate.ItemID = dbPlatform.SQLiteApi.LastInsertRowid(dbConn.Handle); - + if (!blockMap.TryAdd(blockHash, itemTemplate)) { return false; // blockMap add failed @@ -621,7 +622,7 @@ namespace Novacoin return true; } - private bool ConnectBlock(CBlockStoreItem cursor, ref CBlock block, bool fJustCheck=false) + private bool ConnectBlock(CBlockStoreItem cursor, ref CBlock block, bool fJustCheck = false) { // Check it again in case a previous version let a bad block in, but skip BlockSig checking if (!block.CheckBlock(!fJustCheck, !fJustCheck, false)) @@ -694,7 +695,7 @@ namespace Novacoin nFees += nTxValueIn - nTxValueOut; } - if (!ConnectInputs(tx, ref inputs, ref queued, ref cursor, fScriptChecks, scriptFlags)) + if (!ConnectInputs(tx, ref inputs, ref queued, ref cursor, true, fScriptChecks, scriptFlags)) { return false; } @@ -730,7 +731,7 @@ namespace Novacoin } } - cursor.nMint = (long) (nValueOut - nValueIn + nFees); + cursor.nMint = (long)(nValueOut - nValueIn + nFees); cursor.nMoneySupply = (cursor.prev != null ? cursor.prev.nMoneySupply : 0) + (long)nValueOut - (long)nValueIn; if (!UpdateDBCursor(ref cursor)) @@ -746,7 +747,7 @@ namespace Novacoin // Write queued transaction changes var actualMerkleNodes = new Dictionary(); var queuedOutpointItems = new List(); - foreach(KeyValuePair outPair in queued) + foreach (KeyValuePair outPair in queued) { uint256 txID = outPair.Key.hash; CMerkleNode merkleNode; @@ -807,11 +808,140 @@ namespace Novacoin return true; } - private bool ConnectInputs(CTransaction tx, ref Dictionary inputs, ref Dictionary queued, ref CBlockStoreItem cursor, bool fScriptChecks, scriptflag scriptFlags) + private bool ConnectInputs(CTransaction tx, ref Dictionary inputs, ref Dictionary queued, ref CBlockStoreItem cursorBlock, bool fBlock, bool fScriptChecks, scriptflag scriptFlags) { - throw new NotImplementedException(); + // Take over previous transactions' spent pointers + // fBlock is true when this is called from AcceptBlock when a new best-block is added to the blockchain + // fMiner is true when called from the internal bitcoin miner + // ... both are false when called from CTransaction::AcceptToMemoryPool + + if (!tx.IsCoinBase) + { + ulong nValueIn = 0; + ulong nFees = 0; + for (uint i = 0; i < tx.vin.Length; i++) + { + var prevout = tx.vin[i].prevout; + Contract.Assert(inputs.ContainsKey(prevout)); + var input = inputs[prevout]; + + CBlockStoreItem parentBlockCursor; + var merkleItem = GetMerkleCursor(input, out parentBlockCursor); + + if (merkleItem == null) + { + return false; // Unable to find merkle node + } + + // If prev is coinbase or coinstake, check that it's matured + if (merkleItem.IsCoinBase || merkleItem.IsCoinStake) + { + if (cursorBlock.nHeight - parentBlockCursor.nHeight < NetInfo.nGeneratedMaturity) + { + return false; // tried to spend non-matured generation input. + } + } + + // check transaction timestamp + if (merkleItem.nTime > tx.nTime) + { + return false; // transaction timestamp earlier than input transaction + } + + // Check for negative or overflow input values + nValueIn += input.nValue; + if (!CTransaction.MoneyRange(input.nValue) || !CTransaction.MoneyRange(nValueIn)) + { + return false; // txin values out of range + } + + } + + // The first loop above does all the inexpensive checks. + // Only if ALL inputs pass do we perform expensive ECDSA signature checks. + // Helps prevent CPU exhaustion attacks. + for (int i = 0; i < tx.vin.Length; i++) + { + var prevout = tx.vin[i].prevout; + Contract.Assert(inputs.ContainsKey(prevout)); + var input = inputs[prevout]; + + // Check for conflicts (double-spend) + if (input.IsSpent) + { + return false; + } + + // Skip ECDSA signature verification when connecting blocks (fBlock=true) + // before the last blockchain checkpoint. This is safe because block merkle hashes are + // still computed and checked, and any change will be caught at the next checkpoint. + if (fScriptChecks) + { + // Verify signature + if (!ScriptCode.VerifyScript(tx.vin[i].scriptSig, input.scriptPubKey, tx, i, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0)) + { + return false; // VerifyScript failed. + } + } + + // Mark outpoint as spent + input.IsSpent = true; + inputs[prevout] = input; + + // Write back + if (fBlock) + { + queued.Add(prevout, input); + } + } + + if (tx.IsCoinStake) + { + // ppcoin: coin stake tx earns reward instead of paying fee + ulong nCoinAge; + if (!tx.GetCoinAge(ref inputs, out nCoinAge)) + { + return false; // unable to get coin age for coinstake + } + + int nTxSize = (tx.nTime > NetInfo.nStakeValidationSwitchTime) ? tx.Size : 0; + ulong nReward = tx.nValueOut - nValueIn; + + ulong nCalculatedReward = CBlock.GetProofOfStakeReward(nCoinAge, cursorBlock.nBits, tx.nTime) - CTransaction.GetMinFee(1, false, CTransaction.MinFeeMode.GMF_BLOCK, nTxSize) + CTransaction.nCent; + + if (nReward > nCalculatedReward) + { + return false; // coinstake pays too much + } + } + else + { + if (nValueIn < tx.nValueOut) + { + return false; // value in < value out + } + + // Tally transaction fees + ulong nTxFee = nValueIn - tx.nValueOut; + if (nTxFee < 0) + { + return false; // nTxFee < 0 + } + + nFees += nTxFee; + + if (!CTransaction.MoneyRange(nFees)) + { + return false; // nFees out of range + } + } + + } + + return true; } + /// /// Set new top node or current best chain. /// @@ -966,13 +1096,6 @@ namespace Novacoin return false; } - public bool WriteNodes(ref CMerkleNode[] merkleNodes) - { - - - return true; - } - /// /// Get block cursor from map. /// @@ -993,6 +1116,32 @@ namespace Novacoin } /// + /// Get merkle node cursor by output metadata. + /// + /// Output metadata object + /// Merkle node cursor or null + public CMerkleNode GetMerkleCursor(TxOutItem item, out CBlockStoreItem blockCursor) + { + blockCursor = null; + + // Trying to get cursor from the database. + var QueryMerkleCursor = dbConn.Query("select * from [MerkleNodes] where [nMerkleNodeID] = ?", item.nMerkleNodeID); + + if (QueryMerkleCursor.Count == 1) + { + var merkleNode = QueryMerkleCursor[0]; + + // Search for block + var results = blockMap.Where(x => x.Value.ItemID == merkleNode.nParentBlockID).Select(x => x.Value).ToArray(); + + blockCursor = results[0]; + } + + // Nothing found. + return null; + } + + /// /// Load cursor from database. /// /// Block hash diff --git a/Novacoin/CTransaction.cs b/Novacoin/CTransaction.cs index b525dfc..b0d4f08 100644 --- a/Novacoin/CTransaction.cs +++ b/Novacoin/CTransaction.cs @@ -67,6 +67,13 @@ namespace Novacoin /// public const uint nMaxTxSize = 250000; + public enum MinFeeMode + { + GMF_BLOCK, + GMF_RELAY, + GMF_SEND, + } + /// /// Version of transaction schema. /// @@ -545,5 +552,14 @@ namespace Novacoin return new CTxOut(outItem.nValue, outItem.scriptPubKey); } + internal bool GetCoinAge(ref Dictionary inputs, out ulong nCoinAge) + { + throw new NotImplementedException(); + } + + internal static ulong GetMinFee(int v1, bool v2, MinFeeMode gMF_BLOCK, int nTxSize) + { + throw new NotImplementedException(); + } } } diff --git a/Novacoin/DatabaseObjects.cs b/Novacoin/DatabaseObjects.cs index b49d63e..a78f93b 100644 --- a/Novacoin/DatabaseObjects.cs +++ b/Novacoin/DatabaseObjects.cs @@ -566,6 +566,12 @@ namespace Novacoin public long nParentBlockID { get; set; } /// + /// Transaction timestamp + /// + [Column("nTime")] + public uint nTime { get; set; } + + /// /// Transaction type flag /// [Column("TransactionFlags")] @@ -647,8 +653,22 @@ namespace Novacoin private set { TxSize = VarInt.EncodeVarInt(value); } } + [Ignore] + public bool IsCoinBase + { + get { return TransactionFlags == TxFlags.TX_COINBASE; } + } + + [Ignore] + public bool IsCoinStake + { + get { return TransactionFlags == TxFlags.TX_COINSTAKE; } + } + public CMerkleNode(CTransaction tx) { + nTime = tx.nTime; + nTxOffset = -1; nParentBlockID = -1; @@ -657,20 +677,22 @@ namespace Novacoin if (tx.IsCoinBase) { - TransactionFlags |= TxFlags.TX_COINBASE; + TransactionFlags = TxFlags.TX_COINBASE; } else if (tx.IsCoinStake) { - TransactionFlags |= TxFlags.TX_COINSTAKE; + TransactionFlags = TxFlags.TX_COINSTAKE; } else { - TransactionFlags |= TxFlags.TX_USER; + TransactionFlags = TxFlags.TX_USER; } } public CMerkleNode(long nBlockId, long nOffset, CTransaction tx) { + nTime = tx.nTime; + nParentBlockID = nBlockId; nTxOffset = nOffset; diff --git a/Novacoin/NetInfo.cs b/Novacoin/NetInfo.cs index 4e0d519..345d7ae 100644 --- a/Novacoin/NetInfo.cs +++ b/Novacoin/NetInfo.cs @@ -8,6 +8,11 @@ namespace Novacoin internal class NetInfo { /// + /// Minimal depth for spending coinbase and coinstake transactions. + /// + public const int nGeneratedMaturity = 500; + + /// /// "standard" scrypt target limit for proof of work, results with 0,000244140625 proof-of-work difficulty /// public static uint256 nProofOfWorkLimit = ~(new uint256(0)) >> 20; @@ -35,19 +40,24 @@ namespace Novacoin /// /// Fri, 20 Sep 2013 00:00:00 GMT /// - public static uint nChainChecksSwitchTime = 1379635200; + public const uint nChainChecksSwitchTime = 1379635200; + + /// + /// Wed, 20 Aug 2014 00:00:00 GMT + /// + public const uint nStakeValidationSwitchTime = 1408492800; /// /// Hash of block #0 /// public static uint256 nHashGenesisBlock = new uint256("00000a060336cbb72fe969666d337b87198b1add2abaa59cca226820b32933a4"); - public static readonly uint nLockTimeThreshold = 500000000; + public const uint nLockTimeThreshold = 500000000; /// /// Allowed clock drift. /// - private static readonly uint nDrift = 7200; + private const uint nDrift = 7200; /// /// Maximum possible proof-of-work reward. -- 1.7.1